memtrails 0.0.0-stage → 0.2.0

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.
Files changed (76) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +21 -0
  3. package/README.md +221 -2
  4. package/dist/core/consolidate.d.ts +144 -0
  5. package/dist/core/consolidate.js +660 -0
  6. package/dist/core/embedder.d.ts +30 -0
  7. package/dist/core/embedder.js +137 -0
  8. package/dist/core/episodes.d.ts +32 -0
  9. package/dist/core/episodes.js +53 -0
  10. package/dist/core/extract.d.ts +142 -0
  11. package/dist/core/extract.js +452 -0
  12. package/dist/core/gather.d.ts +17 -0
  13. package/dist/core/gather.js +98 -0
  14. package/dist/core/graph.d.ts +48 -0
  15. package/dist/core/graph.js +208 -0
  16. package/dist/core/local-embedder.d.ts +40 -0
  17. package/dist/core/local-embedder.js +98 -0
  18. package/dist/core/model.d.ts +16 -0
  19. package/dist/core/model.js +74 -0
  20. package/dist/core/paths.d.ts +1 -0
  21. package/dist/core/paths.js +7 -0
  22. package/dist/core/profiles.d.ts +30 -0
  23. package/dist/core/profiles.js +147 -0
  24. package/dist/core/qdrant-binary.d.ts +25 -0
  25. package/dist/core/qdrant-binary.js +108 -0
  26. package/dist/core/qdrant-client.d.ts +115 -0
  27. package/dist/core/qdrant-client.js +327 -0
  28. package/dist/core/qdrant-process.d.ts +30 -0
  29. package/dist/core/qdrant-process.js +160 -0
  30. package/dist/core/rebuild.d.ts +31 -0
  31. package/dist/core/rebuild.js +109 -0
  32. package/dist/core/recall-log.d.ts +29 -0
  33. package/dist/core/recall-log.js +82 -0
  34. package/dist/core/recall.d.ts +91 -0
  35. package/dist/core/recall.js +172 -0
  36. package/dist/core/redact.d.ts +2 -0
  37. package/dist/core/redact.js +48 -0
  38. package/dist/core/reranker.d.ts +26 -0
  39. package/dist/core/reranker.js +67 -0
  40. package/dist/core/room.d.ts +20 -0
  41. package/dist/core/room.js +63 -0
  42. package/dist/core/sleep.d.ts +44 -0
  43. package/dist/core/sleep.js +199 -0
  44. package/dist/core/store.d.ts +198 -0
  45. package/dist/core/store.js +190 -0
  46. package/dist/core/text.d.ts +31 -0
  47. package/dist/core/text.js +101 -0
  48. package/dist/core/words.d.ts +32 -0
  49. package/dist/core/words.js +127 -0
  50. package/dist/core/writer.d.ts +36 -0
  51. package/dist/core/writer.js +146 -0
  52. package/dist/importers/files.d.ts +31 -0
  53. package/dist/importers/files.js +157 -0
  54. package/dist/importers/git.d.ts +30 -0
  55. package/dist/importers/git.js +118 -0
  56. package/dist/importers/index.d.ts +14 -0
  57. package/dist/importers/index.js +26 -0
  58. package/dist/importers/jsonl.d.ts +20 -0
  59. package/dist/importers/jsonl.js +65 -0
  60. package/dist/importers/mbox.d.ts +20 -0
  61. package/dist/importers/mbox.js +347 -0
  62. package/dist/importers/slack.d.ts +21 -0
  63. package/dist/importers/slack.js +134 -0
  64. package/dist/importers/text.d.ts +31 -0
  65. package/dist/importers/text.js +191 -0
  66. package/dist/index.d.ts +28 -0
  67. package/dist/index.js +22 -0
  68. package/dist/llm.d.ts +60 -0
  69. package/dist/llm.js +60 -0
  70. package/dist/memory.d.ts +106 -0
  71. package/dist/memory.js +262 -0
  72. package/dist/records.d.ts +62 -0
  73. package/dist/records.js +107 -0
  74. package/dist/tools/agent-tools.d.ts +52 -0
  75. package/dist/tools/agent-tools.js +142 -0
  76. package/package.json +78 -4
@@ -0,0 +1,160 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { closeSync, mkdirSync, openSync, statSync, renameSync, writeFileSync } from 'node:fs';
3
+ import { createServer } from 'node:net';
4
+ import { join, resolve } from 'node:path';
5
+ import { spawn } from 'node:child_process';
6
+ /** A port the OS says is free on loopback. */
7
+ async function freePort() {
8
+ return new Promise((resolve, reject) => {
9
+ const s = createServer();
10
+ s.unref();
11
+ s.once('error', reject);
12
+ s.listen(0, '127.0.0.1', () => {
13
+ const addr = s.address();
14
+ const port = typeof addr === 'object' && addr ? addr.port : 0;
15
+ s.close(() => (port ? resolve(port) : reject(new Error('no free port'))));
16
+ });
17
+ });
18
+ }
19
+ /** The config file: what environment variables cannot say (gRPC off is `grpc_port: null`). */
20
+ export function qdrantConfig(storage, port) {
21
+ return [
22
+ 'log_level: WARN',
23
+ 'telemetry_disabled: true',
24
+ 'service:',
25
+ ' host: 127.0.0.1',
26
+ ` http_port: ${port}`,
27
+ ' grpc_port: null',
28
+ ' enable_cors: false',
29
+ ' max_workers: 1',
30
+ 'storage:',
31
+ ` storage_path: ${JSON.stringify(join(storage, 'storage'))}`,
32
+ ` snapshots_path: ${JSON.stringify(join(storage, 'snapshots'))}`,
33
+ ' on_disk_payload: true',
34
+ ' performance:',
35
+ ' max_search_threads: 2',
36
+ ' optimizers:',
37
+ ' max_optimization_threads: 1',
38
+ '',
39
+ ].join('\n');
40
+ }
41
+ const LOG_MAX = 4 * 1024 * 1024;
42
+ /** The process's log beside its data, rotated once at start when it has grown past 4 MB. */
43
+ function openLog(dir) {
44
+ const path = join(dir, 'qdrant.log');
45
+ try {
46
+ if (statSync(path).size > LOG_MAX)
47
+ renameSync(path, `${path}.1`);
48
+ }
49
+ catch { /* none yet */ }
50
+ mkdirSync(dir, { recursive: true });
51
+ return openSync(path, 'a');
52
+ }
53
+ export class QdrantProcess {
54
+ child = null;
55
+ endpoint = null;
56
+ stopping = false;
57
+ deaths = [];
58
+ restartTimer = null;
59
+ opts;
60
+ /** `dir` may be relative: Qdrant runs inside it, so every path it is given is made absolute first. */
61
+ constructor(opts) {
62
+ this.opts = { ...opts, dir: resolve(opts.dir), binary: opts.binary.includes('/') || opts.binary.includes('\\') ? resolve(opts.binary) : opts.binary };
63
+ }
64
+ /** Where it answers, once it does; null while it starts or after it stopped. */
65
+ current() {
66
+ return this.endpoint;
67
+ }
68
+ async start() {
69
+ this.stopping = false;
70
+ if (this.endpoint && this.child && this.child.exitCode === null)
71
+ return this.endpoint;
72
+ const port = await freePort();
73
+ const apiKey = randomBytes(24).toString('hex');
74
+ mkdirSync(this.opts.dir, { recursive: true });
75
+ const config = join(this.opts.dir, 'config.yaml');
76
+ writeFileSync(config, qdrantConfig(this.opts.dir, port), { mode: 0o600 });
77
+ const fd = openLog(this.opts.dir);
78
+ let child;
79
+ try {
80
+ child = spawn(this.opts.binary, ['--config-path', config, '--disable-telemetry'], {
81
+ cwd: this.opts.dir,
82
+ stdio: ['ignore', fd, fd],
83
+ // Only the key: the rest is in the config file.
84
+ env: { ...process.env, QDRANT__SERVICE__API_KEY: apiKey, QDRANT__TELEMETRY_DISABLED: 'true' },
85
+ });
86
+ }
87
+ finally {
88
+ closeSync(fd);
89
+ }
90
+ this.child = child;
91
+ child.once('exit', (code, signal) => this.exited(child, code, signal));
92
+ const endpoint = { url: `http://127.0.0.1:${port}`, apiKey };
93
+ await this.ready(endpoint, child);
94
+ this.endpoint = endpoint;
95
+ this.opts.log(`[memory] Qdrant up on 127.0.0.1:${port}`);
96
+ return endpoint;
97
+ }
98
+ /** Answering `/readyz` within 30 s, or the start failed. */
99
+ async ready(e, child) {
100
+ const until = Date.now() + 30_000;
101
+ while (Date.now() < until) {
102
+ if (child.exitCode !== null)
103
+ throw new Error(`Qdrant exited (${child.exitCode}) while starting — see ${join(this.opts.dir, 'qdrant.log')}`);
104
+ try {
105
+ const r = await fetch(`${e.url}/readyz`, { headers: { 'api-key': e.apiKey }, signal: AbortSignal.timeout(1000) });
106
+ if (r.ok)
107
+ return;
108
+ }
109
+ catch { /* not listening yet */ }
110
+ await new Promise((r) => setTimeout(r, 100));
111
+ }
112
+ child.kill('SIGTERM');
113
+ throw new Error('Qdrant did not answer within 30 s');
114
+ }
115
+ exited(child, code, signal) {
116
+ if (this.child !== child)
117
+ return;
118
+ this.child = null;
119
+ this.endpoint = null;
120
+ if (this.stopping)
121
+ return;
122
+ const why = `Qdrant stopped (${signal ?? code})`;
123
+ this.opts.log(`[memory] ${why}`);
124
+ const now = Date.now();
125
+ this.deaths = [...this.deaths.filter((t) => now - t < 10 * 60_000), now];
126
+ if (this.deaths.length >= 5) {
127
+ this.opts.onDown?.(`${why}; five times in ten minutes, so it is left stopped`);
128
+ return;
129
+ }
130
+ const pause = Math.min(60_000, 1000 * 2 ** (this.deaths.length - 1));
131
+ this.restartTimer = setTimeout(() => {
132
+ this.restartTimer = null;
133
+ this.start().catch((err) => this.opts.onDown?.(err.message));
134
+ }, pause);
135
+ this.restartTimer.unref?.();
136
+ }
137
+ stop() {
138
+ this.stopping = true;
139
+ if (this.restartTimer) {
140
+ clearTimeout(this.restartTimer);
141
+ this.restartTimer = null;
142
+ }
143
+ const c = this.child;
144
+ this.child = null;
145
+ this.endpoint = null;
146
+ if (c)
147
+ c.kill('SIGTERM');
148
+ }
149
+ /** Stop and wait for the process to be gone — before its storage is deleted. */
150
+ async stopAndWait(ms = 10_000) {
151
+ const c = this.child;
152
+ this.stop();
153
+ if (!c || c.exitCode !== null)
154
+ return;
155
+ await new Promise((resolve) => {
156
+ const t = setTimeout(resolve, ms);
157
+ c.once('exit', () => { clearTimeout(t); resolve(); });
158
+ });
159
+ }
160
+ }
@@ -0,0 +1,31 @@
1
+ import type { LtmStore, MemoryItem, StmEntry, StmStore } from './store.js';
2
+ interface RebuildDeps {
3
+ path: string;
4
+ ltm: LtmStore;
5
+ stm: StmStore;
6
+ writeLtm: (items: MemoryItem[]) => Promise<void>;
7
+ writeStm: (items: StmEntry[]) => Promise<void>;
8
+ aborted: () => boolean;
9
+ now?: () => number;
10
+ /** Batches written at once (1 when not given). The checkpoint only ever passes batches already written: it moves over
11
+ * the written ones at the front, so a crash never skips one still in flight. */
12
+ concurrency?: number;
13
+ }
14
+ export declare class MemoryRebuild {
15
+ private readonly d;
16
+ private running;
17
+ private generation;
18
+ constructor(d: RebuildDeps);
19
+ cancel(): void;
20
+ settled(): Promise<void>;
21
+ pending(): boolean;
22
+ /** Written BEFORE clearing an old index. A crash at any later point leaves a resumable rebuild. */
23
+ begin(): void;
24
+ run(): Promise<void>;
25
+ private save;
26
+ /** One phase from its checkpoint: batches of `size`, up to `concurrency` written at once, the checkpoint moved over
27
+ * the written batches at the front. False when cancelled. A failed batch fails the run, the checkpoint before it. */
28
+ private phase;
29
+ private resume;
30
+ }
31
+ export {};
@@ -0,0 +1,109 @@
1
+ /** A derived index rebuild survives unavailable embeddings, a failed batch and a restart. Logs stay truth. */
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
4
+ export class MemoryRebuild {
5
+ d;
6
+ running = null;
7
+ generation = 0;
8
+ constructor(d) {
9
+ this.d = d;
10
+ }
11
+ cancel() { this.generation++; }
12
+ async settled() { await this.running?.catch(() => { }); }
13
+ pending() { return existsSync(this.d.path); }
14
+ /** Written BEFORE clearing an old index. A crash at any later point leaves a resumable rebuild. */
15
+ begin() { this.save({ phase: 'ltm', after: null }); }
16
+ run() {
17
+ this.running ??= this.resume(this.generation).finally(() => { this.running = null; });
18
+ return this.running;
19
+ }
20
+ save(checkpoint) {
21
+ mkdirSync(dirname(this.d.path), { recursive: true });
22
+ const tmp = `${this.d.path}.tmp`;
23
+ writeFileSync(tmp, JSON.stringify(checkpoint), { mode: 0o600 });
24
+ renameSync(tmp, this.d.path);
25
+ }
26
+ /** One phase from its checkpoint: batches of `size`, up to `concurrency` written at once, the checkpoint moved over
27
+ * the written batches at the front. False when cancelled. A failed batch fails the run, the checkpoint before it. */
28
+ async phase(all, after, keep, size, write, phase, cancelled) {
29
+ const limit = Math.max(1, this.d.concurrency ?? 1);
30
+ const queue = [];
31
+ const advance = () => {
32
+ let last = null;
33
+ while (queue.length && queue[0].done)
34
+ last = queue.shift().last;
35
+ if (last !== null && !cancelled())
36
+ this.save({ phase, after: last });
37
+ };
38
+ // On a failure: the batches still in flight settle first, and the checkpoint takes every written one at the front.
39
+ const fail = async (err) => {
40
+ await Promise.allSettled(queue.map((q) => q.p));
41
+ advance();
42
+ throw err;
43
+ };
44
+ const send = async (items) => {
45
+ const slot = { last: items.at(-1).uid, done: false, p: Promise.resolve() };
46
+ slot.p = write(items).then(() => { slot.done = true; });
47
+ slot.p.catch(() => { }); // awaited below; a batch left in flight by a failure elsewhere is not unhandled
48
+ queue.push(slot);
49
+ for (let busy = queue.filter((q) => !q.done); busy.length >= limit; busy = queue.filter((q) => !q.done)) {
50
+ await Promise.race(busy.map((q) => q.p)).catch(fail);
51
+ advance();
52
+ }
53
+ };
54
+ let skip = after !== null;
55
+ let batch = [];
56
+ for (const it of all) {
57
+ if (cancelled())
58
+ return false;
59
+ if (skip) {
60
+ if (it.uid === after)
61
+ skip = false;
62
+ continue;
63
+ }
64
+ if (!keep(it))
65
+ continue;
66
+ batch.push(it);
67
+ if (batch.length >= size) {
68
+ await send(batch);
69
+ batch = [];
70
+ }
71
+ }
72
+ if (batch.length)
73
+ await send(batch);
74
+ await Promise.all(queue.map((q) => q.p)).catch(fail);
75
+ advance();
76
+ return !cancelled();
77
+ }
78
+ async resume(generation) {
79
+ const cancelled = () => generation !== this.generation || this.d.aborted();
80
+ if (!this.pending() || cancelled())
81
+ return;
82
+ let checkpoint;
83
+ try {
84
+ checkpoint = JSON.parse(readFileSync(this.d.path, 'utf8'));
85
+ if (!['ltm', 'stm'].includes(checkpoint.phase) || (checkpoint.after !== null && typeof checkpoint.after !== 'string'))
86
+ throw new Error('invalid checkpoint');
87
+ }
88
+ catch {
89
+ checkpoint = { phase: 'ltm', after: null };
90
+ }
91
+ // While rebuilding, nothing else writes to the logs. Live log order therefore stays stable; if a
92
+ // checkpoint's item is absent (a repaired/compacted log), replay that phase instead of silently skipping records.
93
+ if (checkpoint.after && !(checkpoint.phase === 'ltm' ? this.d.ltm : this.d.stm).meta(checkpoint.after))
94
+ checkpoint.after = null;
95
+ if (checkpoint.phase === 'ltm') {
96
+ const done = await this.phase(this.d.ltm.all(), checkpoint.after, (it) => it.status === 'active', 20, this.d.writeLtm, 'ltm', cancelled);
97
+ if (!done)
98
+ return;
99
+ checkpoint = { phase: 'stm', after: null };
100
+ this.save(checkpoint);
101
+ }
102
+ // Every request the person made stays searchable, consolidated or not: the raw record is the truth, a memory a key
103
+ // to it (LongMemEval 2410.10813). Only throwaway runs (synthetic) stay out.
104
+ if (!(await this.phase(this.d.stm.all(), checkpoint.after, (it) => !it.synthetic, 32, this.d.writeStm, 'stm', cancelled)))
105
+ return;
106
+ if (!cancelled())
107
+ rmSync(this.d.path);
108
+ }
109
+ }
@@ -0,0 +1,29 @@
1
+ /** A query kept up to this: the start of a long message is what the search embedded first. */
2
+ export declare const RECALL_QUERY_CHARS = 2000;
3
+ export declare const RECALL_KEEP_MS: number;
4
+ export declare const RECALL_KEEP_BYTES: number;
5
+ export type RecallEvent = {
6
+ ev: 'search';
7
+ project?: string;
8
+ query: string;
9
+ shown: string[];
10
+ } | {
11
+ ev: 'open';
12
+ item: string;
13
+ };
14
+ export declare class RecallBook {
15
+ private readonly path;
16
+ private readonly now;
17
+ private readonly keep;
18
+ private chain;
19
+ constructor(path: string, now?: () => number, keep?: {
20
+ ms: number;
21
+ bytes: number;
22
+ });
23
+ record(e: RecallEvent): void;
24
+ /** The events older than `keep.ms` dropped, then the oldest until the rest fit `keep.bytes` — rewritten between
25
+ * appends (a new file renamed over the old). How many were dropped; 0 on any failure, the record as it was. */
26
+ trim(): Promise<number>;
27
+ /** Every event recorded so far written (tests, shutdown). */
28
+ settled(): Promise<void>;
29
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * What recall showed, for what, and what was opened after it — the record a model that learns from USE reads.
3
+ *
4
+ * Learned only from what memory says and how it is laid out, two light models did not beat the first stage on the
5
+ * answers shown (docs/RESEARCH.md: a hetero-associative map over the memory's own pairs, a LightGBM
6
+ * ranker over language-free association features). What works in the literature is learned from use — links that
7
+ * strengthen when memories are recalled together and fade when not (HeLa-Mem, ACL 2026), rankers trained on what was
8
+ * opened (online learning to rank). The usage counts (store.ts UsageBook) say how often, never with what nor for what;
9
+ * this says both.
10
+ *
11
+ * One line per event in `recall.jsonl` beside the logs — on this machine only, cleared with the index, never sent:
12
+ * {"t", "ev": "search", "project", "query", "shown": [ids]} memory_search
13
+ * {"t", "ev": "open", "item"} memory_open
14
+ * Appended in order, a line at a time; a write that fails is dropped (recall never waits on, or fails for, its record).
15
+ * The ids are the logs' own (`stm:…`, long-term uids): a rebuild from the logs keeps them (only `qdrant/` is made
16
+ * again), so the record outlives a rebuild and stays true to it. Kept: the last RECALL_KEEP_MS, at most
17
+ * RECALL_KEEP_BYTES of the newest (`trim()`).
18
+ */
19
+ import { appendFile, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
20
+ import { dirname } from 'node:path';
21
+ /** A query kept up to this: the start of a long message is what the search embedded first. */
22
+ export const RECALL_QUERY_CHARS = 2000;
23
+ export const RECALL_KEEP_MS = 180 * 24 * 60 * 60_000;
24
+ export const RECALL_KEEP_BYTES = 32 * 1024 * 1024;
25
+ export class RecallBook {
26
+ path;
27
+ now;
28
+ keep;
29
+ chain = Promise.resolve();
30
+ constructor(path, now = Date.now, keep = { ms: RECALL_KEEP_MS, bytes: RECALL_KEEP_BYTES }) {
31
+ this.path = path;
32
+ this.now = now;
33
+ this.keep = keep;
34
+ }
35
+ record(e) {
36
+ const line = `${JSON.stringify({ t: this.now(), ...e, ...('query' in e ? { query: e.query.slice(0, RECALL_QUERY_CHARS) } : {}) })}\n`;
37
+ this.chain = this.chain
38
+ .then(async () => {
39
+ await mkdir(dirname(this.path), { recursive: true });
40
+ await appendFile(this.path, line, { mode: 0o600 });
41
+ })
42
+ .catch(() => { });
43
+ }
44
+ /** The events older than `keep.ms` dropped, then the oldest until the rest fit `keep.bytes` — rewritten between
45
+ * appends (a new file renamed over the old). How many were dropped; 0 on any failure, the record as it was. */
46
+ trim() {
47
+ const run = this.chain.then(async () => {
48
+ let text;
49
+ try {
50
+ text = await readFile(this.path, 'utf8');
51
+ }
52
+ catch {
53
+ return 0;
54
+ }
55
+ const lines = text.split('\n').filter(Boolean);
56
+ const since = this.now() - this.keep.ms;
57
+ const recent = lines.filter((l) => { try {
58
+ return Number(JSON.parse(l).t) >= since;
59
+ }
60
+ catch {
61
+ return false;
62
+ } });
63
+ let bytes = recent.reduce((n, l) => n + Buffer.byteLength(l, 'utf8') + 1, 0);
64
+ let from = 0;
65
+ while (bytes > this.keep.bytes && from < recent.length)
66
+ bytes -= Buffer.byteLength(recent[from++], 'utf8') + 1;
67
+ const kept = recent.slice(from);
68
+ if (kept.length === lines.length)
69
+ return 0;
70
+ const next = `${this.path}.trim`;
71
+ await writeFile(next, kept.map((l) => `${l}\n`).join(''), { mode: 0o600 });
72
+ await rename(next, this.path);
73
+ return lines.length - kept.length;
74
+ });
75
+ this.chain = run.then(() => { }, () => { });
76
+ return run.catch(() => 0);
77
+ }
78
+ /** Every event recorded so far written (tests, shutdown). */
79
+ settled() {
80
+ return this.chain;
81
+ }
82
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Recall: what a search returns, an item opened whole, and exploring around a topic.
3
+ *
4
+ * `memorySearch` embeds the query and searches dense vectors (each item's key, its chunks and its cues) and a keyword
5
+ * vector at once, fused by rank and grouped by item; with dates, what happened within them first. The best 30 can be
6
+ * reordered (`rerank`: word by word on this machine, words.ts, or any cross-encoder) and fused with the search's own order.
7
+ * `memoryOpen` reads one item whole. `memoryExplore` walks the concept graph (graph.ts) from a topic or from items to
8
+ * what is related but not found directly.
9
+ */
10
+ import { type QdrantClient } from './qdrant-client.js';
11
+ import type { RecallEvent } from './recall-log.js';
12
+ export interface RecallDeps {
13
+ client: QdrantClient;
14
+ embed: (texts: string[], timeoutMs?: number) => Promise<Float32Array[]>;
15
+ /** What was shown / opened: the sleep pass's "use" (store.ts UsageBook). */
16
+ used?: (items: string[], what: 'shown' | 'opened') => void;
17
+ /** What each search showed, for what, and what was opened (recall-log.ts). */
18
+ record?: (e: RecallEvent) => void;
19
+ now?: () => number;
20
+ /** Each document's relevance to the query, or null — the search then keeps its own order. None: no reordering. */
21
+ rerank?: (query: string, documents: string[], timeoutMs: number) => Promise<number[] | null>;
22
+ }
23
+ /** Where a found item came from, when it is a record you added. */
24
+ export interface RecordRef {
25
+ id: string;
26
+ source?: string;
27
+ author?: string;
28
+ url?: string;
29
+ metadata?: Record<string, string | number | boolean | null>;
30
+ }
31
+ export interface RecalledItem {
32
+ id: string;
33
+ title: string;
34
+ summary: string;
35
+ where: string;
36
+ project: string;
37
+ engine: string;
38
+ ts: number;
39
+ /** For a record you added: its kind (`email`, `message`, …, `record` when none was given) and `record` says which.
40
+ * For what consolidation learned: decision, procedure, pitfall, fact, preference, …, observation (one dated thing a
41
+ * thread told), profile (what is known about one subject), episode (a kept summary of earlier work). */
42
+ kind: string;
43
+ /** The record it is, for a record you added. */
44
+ record?: RecordRef;
45
+ /** For an observation: when it happened (YYYY-MM-DD, YYYY-MM or YYYY) — `ts` is when it was said. */
46
+ eventAt?: string;
47
+ /** For an observation of something that lasted: when it began and ended. */
48
+ startAt?: string;
49
+ endAt?: string;
50
+ /** The passage of its text that matched the search, when that was not its key (memory_search): a fact said deep in
51
+ * a long request is shown, not only the request's start. */
52
+ match?: string;
53
+ /** The search's fused rank score. */
54
+ score: number;
55
+ }
56
+ export declare const MAX_SEARCH = 200;
57
+ /** A search: best first, in every collection or the ones named — and with dates, what happened within them first, then
58
+ * the rest. Never only what lies within them: on 64 held-out questions that name a time, the answer reached the reader
59
+ * whole for 54 searched without dates, 53 with what lies within them first, 44 with only that. */
60
+ export interface SearchQuery {
61
+ query: string;
62
+ /** Only these collections (records' `collection`). Default: all. */
63
+ collections?: string[];
64
+ /** Only these kinds (records' `kind`, or what consolidation learned: `observation`, `fact`, …). */
65
+ kinds?: string[];
66
+ limit?: number;
67
+ from?: string;
68
+ to?: string;
69
+ }
70
+ export declare function memorySearch(d: RecallDeps, q: SearchQuery): Promise<RecalledItem[]>;
71
+ export interface ExploreQuery {
72
+ topic?: string;
73
+ from?: string[];
74
+ collections?: string[];
75
+ limit?: number;
76
+ }
77
+ export interface Explored {
78
+ /** What search found for the topic, best first. */
79
+ found: RecalledItem[];
80
+ /** Reached through the graph and not found by search, best first, with the way it was reached. */
81
+ related: Array<RecalledItem & {
82
+ via: string;
83
+ }>;
84
+ /** The concepts around it: where to look next. */
85
+ concepts: string[];
86
+ }
87
+ /** An agent exploring: from a topic (search finds the first items; the topic's concepts start the walk too) or from
88
+ * items, what the concept graph reaches that search did not, and the concepts around it. */
89
+ export declare function memoryExplore(d: RecallDeps, q: ExploreQuery): Promise<Explored>;
90
+ /** One item whole, or null. */
91
+ export declare function memoryOpen(d: RecallDeps, id: string): Promise<Record<string, unknown> | null>;