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,30 @@
1
+ export declare const MEMORY_EMBEDDER: {
2
+ readonly id: "embeddinggemma-300m-768";
3
+ readonly model: "embeddinggemma-300m";
4
+ readonly dims: 768;
5
+ };
6
+ /** EmbeddingGemma's task prompts: a query and a document are embedded with different prefixes. */
7
+ export declare const docText: (title: string, text: string) => string;
8
+ export declare const queryText: (q: string) => string;
9
+ export interface EmbedRoute {
10
+ /** The API's base, e.g. `http://localhost:8080/v1` (`/embeddings` is added). */
11
+ url: string;
12
+ apiKey?: string;
13
+ /** The model name the service knows it by. Default `embeddinggemma-300m`. */
14
+ model?: string;
15
+ /** Send bodies gzipped (a service that accepts `content-encoding: gzip`). */
16
+ gzip?: boolean;
17
+ }
18
+ /** The route the environment names: MEMTRAILS_EMBED_URL, MEMTRAILS_EMBED_KEY, MEMTRAILS_EMBED_MODEL; null without one. */
19
+ export declare function resolveEmbedRoute(env?: NodeJS.ProcessEnv): EmbedRoute | null;
20
+ /** No way to embed: neither a model directory nor a server was given. */
21
+ export declare class EmbeddingUnavailable extends Error {
22
+ }
23
+ export interface EmbedderDeps {
24
+ route?: () => EmbedRoute | null;
25
+ fetchImpl?: typeof fetch;
26
+ /** Total operation budget, including compression, retries and all batches (default 30 s). */
27
+ timeoutMs?: number;
28
+ }
29
+ /** `embed(texts)` for memory: unit vectors in the order of `texts`, however many requests a batch needs. */
30
+ export declare function remoteEmbedder(d?: EmbedderDeps): (texts: string[], timeoutMs?: number) => Promise<Float32Array[]>;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Embedding over HTTP, for a memory that embeds on a server: any service that speaks the OpenAI embeddings API
3
+ * (`POST {url}/embeddings`, `{ model, input }`) and serves EmbeddingGemma-300m — text-embeddings-inference, Infinity,
4
+ * Ollama (`embeddinggemma`), vLLM, your own. Vectors may come back as numbers or as little-endian float32 base64.
5
+ *
6
+ * Memory's prompts, indexes and measured thresholds are EmbeddingGemma's: a different model needs its own index
7
+ * (`MemoryOptions.embed` with `dims`), and its results are not the measured ones.
8
+ */
9
+ import { promisify } from 'node:util';
10
+ import { gzip } from 'node:zlib';
11
+ export const MEMORY_EMBEDDER = { id: 'embeddinggemma-300m-768', model: 'embeddinggemma-300m', dims: 768 };
12
+ /** EmbeddingGemma's task prompts: a query and a document are embedded with different prefixes. */
13
+ export const docText = (title, text) => `title: ${title || 'none'} | text: ${text}`;
14
+ export const queryText = (q) => `task: search result | query: ${q}`;
15
+ /** The route the environment names: MEMTRAILS_EMBED_URL, MEMTRAILS_EMBED_KEY, MEMTRAILS_EMBED_MODEL; null without one. */
16
+ export function resolveEmbedRoute(env = process.env) {
17
+ const url = env.MEMTRAILS_EMBED_URL?.trim();
18
+ if (!url)
19
+ return null;
20
+ return { url, apiKey: env.MEMTRAILS_EMBED_KEY?.trim() || undefined, model: env.MEMTRAILS_EMBED_MODEL?.trim() || undefined };
21
+ }
22
+ /** No way to embed: neither a model directory nor a server was given. */
23
+ export class EmbeddingUnavailable extends Error {
24
+ }
25
+ const MAX_INPUTS = 256;
26
+ const MAX_BYTES = 600_000; // JSON-escaped UTF-8 bytes in one request.
27
+ const MAX_TEXT = 24_000;
28
+ const IN_FLIGHT = 4;
29
+ const compress = promisify(gzip);
30
+ /** Validate the complete response before any vector reaches the index. Never mix dimensions or vector spaces. */
31
+ function vectors(body, count) {
32
+ const data = [...(body.data ?? [])].sort((a, b) => a.index - b.index);
33
+ if (data.length !== count)
34
+ throw new Error(`embeddings: ${data.length} vectors for ${count} texts`);
35
+ return data.map(({ index, embedding }, n) => {
36
+ if (index !== n)
37
+ throw new Error('embeddings: missing or duplicate response index');
38
+ let v;
39
+ if (typeof embedding === 'string') {
40
+ const bytes = Buffer.from(embedding, 'base64');
41
+ if (bytes.length !== MEMORY_EMBEDDER.dims * 4 || bytes.toString('base64') !== embedding)
42
+ throw new Error('embeddings: invalid base64 vector');
43
+ v = new Float32Array(MEMORY_EMBEDDER.dims);
44
+ for (let i = 0; i < v.length; i++)
45
+ v[i] = bytes.readFloatLE(i * 4);
46
+ }
47
+ else {
48
+ if (!Array.isArray(embedding) || embedding.length !== MEMORY_EMBEDDER.dims || embedding.some((x) => typeof x !== 'number'))
49
+ throw new Error('embeddings: wrong vector dimensions');
50
+ v = Float32Array.from(embedding);
51
+ }
52
+ const norm = Math.hypot(...v);
53
+ if (!Number.isFinite(norm) || norm <= 0)
54
+ throw new Error('embeddings: non-finite or zero vector');
55
+ for (let i = 0; i < v.length; i++)
56
+ v[i] /= norm;
57
+ return v;
58
+ });
59
+ }
60
+ /** `embed(texts)` for memory: unit vectors in the order of `texts`, however many requests a batch needs. */
61
+ export function remoteEmbedder(d = {}) {
62
+ const route = d.route ?? (() => resolveEmbedRoute());
63
+ return async (texts, timeoutMs = d.timeoutMs ?? 30_000) => {
64
+ if (!texts.length)
65
+ return [];
66
+ const r = route();
67
+ if (!r)
68
+ throw new EmbeddingUnavailable('no embedder: give openMemory a modelDir, an embedServer, or set MEMTRAILS_EMBED_URL');
69
+ const until = Date.now() + timeoutMs;
70
+ const remaining = () => {
71
+ const ms = Math.floor(until - Date.now());
72
+ if (ms < 1)
73
+ throw new Error('embeddings: operation deadline exceeded');
74
+ return ms;
75
+ };
76
+ const input = texts.map((t) => t.slice(0, MAX_TEXT) || ' ');
77
+ const parts = [];
78
+ let cur = [];
79
+ let bytes = 0;
80
+ input.forEach((t, i) => {
81
+ const size = Buffer.byteLength(JSON.stringify(t)) + 1;
82
+ if (cur.length && (cur.length >= MAX_INPUTS || bytes + size > MAX_BYTES)) {
83
+ parts.push({ at: i - cur.length, texts: cur });
84
+ cur = [];
85
+ bytes = 0;
86
+ }
87
+ cur.push(t);
88
+ bytes += size;
89
+ });
90
+ if (cur.length)
91
+ parts.push({ at: input.length - cur.length, texts: cur });
92
+ const call = async (part) => {
93
+ remaining();
94
+ const json = Buffer.from(JSON.stringify({ model: r.model ?? MEMORY_EMBEDDER.model, input: part.texts }));
95
+ const body = r.gzip ? await compress(json) : json;
96
+ const url = `${r.url.replace(/\/+$/, '').replace(/\/embeddings$/, '')}/embeddings`;
97
+ for (let attempt = 0;; attempt++) {
98
+ const res = await (d.fetchImpl ?? fetch)(url, {
99
+ method: 'POST',
100
+ signal: AbortSignal.timeout(remaining()),
101
+ headers: { 'content-type': 'application/json', ...(r.gzip ? { 'content-encoding': 'gzip' } : {}), ...(r.apiKey ? { authorization: `Bearer ${r.apiKey}` } : {}) },
102
+ body: new Uint8Array(body),
103
+ });
104
+ if (res.ok) {
105
+ const payload = await res.json();
106
+ remaining();
107
+ vectors(payload, part.texts.length).forEach((v, k) => { out[part.at + k] = v; });
108
+ return;
109
+ }
110
+ const message = (await res.text()).slice(0, 200); // Drain the response before retrying (keep-alive socket).
111
+ if ((res.status === 429 || res.status >= 500) && attempt < 3) {
112
+ const delay = 500 * 2 ** attempt;
113
+ if (remaining() <= delay)
114
+ throw new Error('embeddings: operation deadline exceeded');
115
+ await new Promise((ok) => setTimeout(ok, delay));
116
+ continue;
117
+ }
118
+ throw new Error(`embeddings ${res.status}: ${message}`);
119
+ }
120
+ };
121
+ const out = new Array(input.length);
122
+ let next = 0;
123
+ let failed = false;
124
+ await Promise.all(Array.from({ length: Math.min(IN_FLIGHT, parts.length) }, async () => {
125
+ while (!failed && next < parts.length) {
126
+ try {
127
+ await call(parts[next++]);
128
+ }
129
+ catch (err) {
130
+ failed = true;
131
+ throw err;
132
+ }
133
+ }
134
+ }));
135
+ return out;
136
+ };
137
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * How a record reads: to the index (its body, every word of which is searched in chunks) and to the consolidation model.
3
+ *
4
+ * A conversation's exchange keeps the format consolidation was measured on: what was asked, the steps, what was said.
5
+ * Any other record reads as itself: its title, then its text.
6
+ */
7
+ /** What the index and the consolidation model read of a record (store.ts StmEntry). */
8
+ export interface ReadableRequest {
9
+ uid: string;
10
+ /** The record's kind; `conversation` for an exchange of a conversation. */
11
+ engine?: string;
12
+ typed: 'human' | 'unknown';
13
+ request: string;
14
+ reply: string;
15
+ /** Everything the other side said in an exchange, whole. */
16
+ said: string;
17
+ steps: string[];
18
+ files: string[];
19
+ ts: number | null;
20
+ }
21
+ export declare const isConversation: (e: {
22
+ engine?: string;
23
+ }) => boolean;
24
+ /** A record's body: what its key summarises, its chunks are cut from, and opening it shows — whole, never shortened. */
25
+ export declare function episodeBody(e: Pick<ReadableRequest, 'request' | 'reply' | 'files'> & Partial<Pick<ReadableRequest, 'said' | 'steps' | 'engine'>>): string;
26
+ /** For each day a request was made, the days a time said relatively that day means — yesterday, the week before, the
27
+ * last weekend, each of the six days before by name, this week, last month, last year (UTC as the requests' times) —
28
+ * so the model reads "last week" or "on Friday" off a line instead of counting. "Last week" is the seven days before,
29
+ * as people mean it, not the Monday-to-Sunday week before this one: measured on LoCoMo, the calendar week put events
30
+ * said on a Sunday thirteen days back, a week early. */
31
+ export declare function calendarOf(tss: number[]): string;
32
+ export declare function renderForExtraction(e: ReadableRequest, n: number): string;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * How a record reads: to the index (its body, every word of which is searched in chunks) and to the consolidation model.
3
+ *
4
+ * A conversation's exchange keeps the format consolidation was measured on: what was asked, the steps, what was said.
5
+ * Any other record reads as itself: its title, then its text.
6
+ */
7
+ export const isConversation = (e) => e.engine === 'conversation';
8
+ /** A record's body: what its key summarises, its chunks are cut from, and opening it shows — whole, never shortened. */
9
+ export function episodeBody(e) {
10
+ if (e.engine !== undefined && !isConversation(e))
11
+ return e.reply.trim() ? `${e.request}\n${e.reply}` : e.request;
12
+ const parts = [`Asked: ${e.request}`];
13
+ if (e.steps?.length)
14
+ parts.push(`Steps:\n${e.steps.join('\n')}`);
15
+ parts.push(`Done: ${e.said?.trim() ? e.said : e.reply}`);
16
+ if (e.files.length)
17
+ parts.push(`Files: ${e.files.join(', ')}`);
18
+ return parts.join('\n');
19
+ }
20
+ /** For each day a request was made, the days a time said relatively that day means — yesterday, the week before, the
21
+ * last weekend, each of the six days before by name, this week, last month, last year (UTC as the requests' times) —
22
+ * so the model reads "last week" or "on Friday" off a line instead of counting. "Last week" is the seven days before,
23
+ * as people mean it, not the Monday-to-Sunday week before this one: measured on LoCoMo, the calendar week put events
24
+ * said on a Sunday thirteen days back, a week early. */
25
+ export function calendarOf(tss) {
26
+ const DAY = 86_400_000;
27
+ const iso = (t) => new Date(t).toISOString().slice(0, 10);
28
+ const wd = (t) => new Date(t).toLocaleDateString('en-GB', { weekday: 'short', timeZone: 'UTC' });
29
+ const days = [...new Set(tss.filter((t) => t > 0).map((t) => Math.floor(t / DAY) * DAY))].sort((a, b) => a - b);
30
+ return days.map((d) => {
31
+ const dt = new Date(d);
32
+ const monday = d - ((dt.getUTCDay() + 6) % 7) * DAY;
33
+ const sunday = d - (dt.getUTCDay() === 0 ? 7 : dt.getUTCDay()) * DAY;
34
+ const lastMonth = new Date(Date.UTC(dt.getUTCFullYear(), dt.getUTCMonth() - 1, 1)).toISOString().slice(0, 7);
35
+ const before = [2, 3, 4, 5, 6, 7].map((n) => `${wd(d - n * DAY)} ${iso(d - n * DAY)}`).join(', ');
36
+ return `- ${wd(d)} ${iso(d)}: yesterday ${wd(d - DAY)} ${iso(d - DAY)} · the days before ${before} · last week (the week before) ${iso(d - 7 * DAY)} to ${iso(d - DAY)} · last weekend ${iso(sunday - DAY)} to ${iso(sunday)} · this week ${iso(monday)} to ${iso(monday + 6 * DAY)} · last month ${lastMonth} · last year ${dt.getUTCFullYear() - 1}`;
37
+ }).join('\n');
38
+ }
39
+ export function renderForExtraction(e, n) {
40
+ const when = e.ts ? new Date(e.ts).toISOString().slice(0, 16).replace('T', ' ') : '';
41
+ // Whole: a record too long for one window is read in parts and its parts joined (extract.ts), never shortened.
42
+ if (e.engine !== undefined && !isConversation(e))
43
+ return [`## Record ${n} · ${when} · ${e.engine}`, e.request, e.reply].filter(Boolean).join('\n');
44
+ const who = e.typed === 'human' ? 'typed by the person' : 'sent to the agent';
45
+ const parts = [`## Request ${n} · ${when} · ${who}`, e.request];
46
+ if (e.steps.length)
47
+ parts.push(`Steps:\n${e.steps.join('\n')}`);
48
+ if (e.said)
49
+ parts.push(`Agent said:\n${e.said}`);
50
+ if (e.files.length)
51
+ parts.push(`Files changed: ${e.files.join(', ')}`);
52
+ return parts.join('\n');
53
+ }
@@ -0,0 +1,142 @@
1
+ import { type Ask } from '../llm.js';
2
+ import { type ReadableRequest } from './episodes.js';
3
+ import type { MemoryKind, MemoryOrigin, MemoryScope } from './store.js';
4
+ export declare const WINDOW_CHARS = 60000;
5
+ /** The answer's room. A window's kept episodes, items, cues and concepts measured on a whole real history: 7,056
6
+ * output tokens at the median, 9,223 at p90 — and 3% of windows ran past 12,000, cut off mid-JSON. A cut answer is
7
+ * no answer, and the worker asks the same (newest) window again: one long window stalled consolidation for good. */
8
+ export declare const EXTRACT_MAX_TOKENS = 32000;
9
+ declare const OUTCOMES: readonly ["success", "partial", "fail", "uncertain"];
10
+ export type Outcome = (typeof OUTCOMES)[number];
11
+ declare const PHASES: readonly ["done", "plan", "ongoing", "state"];
12
+ export interface Candidate {
13
+ kind: MemoryKind;
14
+ title: string;
15
+ summary: string;
16
+ body: string;
17
+ appliesWhen: string;
18
+ scope: MemoryScope;
19
+ /** Episode uids it was learned from. */
20
+ provenance: string[];
21
+ confidence: 'verified' | 'stated';
22
+ importance: number;
23
+ /** Requests that would need it (the retrieval cues). */
24
+ cues: string[];
25
+ /** What it is about, as the conversation names it (the graph's concepts). */
26
+ concepts: string[];
27
+ /** Where it came from, when a guard checked it (ConsolidateDeps.guard). */
28
+ origin?: MemoryOrigin;
29
+ }
30
+ /** One dated thing the conversation told — kept as said, never merged with a different one (the Mem0 lesson: a list
31
+ * question needs every event, and a date question the event's own date). */
32
+ export interface Observation {
33
+ text: string;
34
+ about: string;
35
+ /** YYYY-MM-DD, YYYY-MM or YYYY — or empty. */
36
+ eventAt: string;
37
+ /** For what lasts: when it began and when it ended — each YYYY-MM-DD, YYYY-MM or YYYY; absent when not given. */
38
+ startAt?: string;
39
+ endAt?: string;
40
+ phase: (typeof PHASES)[number];
41
+ /** Episode uids it was told in. */
42
+ provenance: string[];
43
+ concepts: string[];
44
+ /** Questions it answers, in the asker's words (searched beside its text, quieter). */
45
+ cues: string[];
46
+ origin?: MemoryOrigin;
47
+ }
48
+ export interface KeptEpisode {
49
+ uid: string;
50
+ title: string;
51
+ summary: string;
52
+ cues: string[];
53
+ concepts: string[];
54
+ outcome?: Outcome;
55
+ origin?: MemoryOrigin;
56
+ }
57
+ /** A cue list as memory keeps it: non-empty, scrubbed, no repeats — every one the model wrote, whole. */
58
+ export declare function cleanCues(raw: unknown): string[];
59
+ /** A concept list as memory keeps it: named as written (scrubbed), one per concept key — every one, whole. */
60
+ export declare function cleanConcepts(raw: unknown): string[];
61
+ export interface Extraction {
62
+ summary: string;
63
+ candidates: Candidate[];
64
+ episodes: KeptEpisode[];
65
+ observations: Observation[];
66
+ dropped: number;
67
+ }
68
+ type RawObservation = {
69
+ text?: string;
70
+ about?: string;
71
+ event_date?: string;
72
+ start_date?: string;
73
+ end_date?: string;
74
+ phase?: string;
75
+ requests?: number[];
76
+ concepts?: unknown;
77
+ cues?: unknown;
78
+ };
79
+ /** The observations as memory keeps them: with text, from a request of this part, scrubbed; a date only when it is one. */
80
+ export declare function cleanObservations(raw: RawObservation[], uidOf: (n: number) => string | undefined): Observation[];
81
+ /** The windows a conversation's pending requests are read in: whole requests, at most WINDOW_CHARS — a request longer
82
+ * than that alone in its window, read there in parts (extractWindow). */
83
+ export declare function windowsOf<T extends ReadableRequest>(eps: T[], max?: number): T[][];
84
+ type RawItem = {
85
+ kind?: string;
86
+ title?: string;
87
+ summary?: string;
88
+ body?: string;
89
+ applies_when?: string;
90
+ scope?: string;
91
+ requests?: number[];
92
+ confidence?: string;
93
+ importance?: number;
94
+ contested?: boolean;
95
+ ambiguous?: boolean;
96
+ cues?: unknown;
97
+ concepts?: unknown;
98
+ };
99
+ /** What survives the gate. Pure: the rules, in one place. `outcomeOf` is how each request ended (the
100
+ * episodes' outcomes): a kind that says "this works" is dropped when every request it came from failed or is unknown. */
101
+ export declare function gate(raw: RawItem[], uidOf: (n: number) => string | undefined, outcomeOf?: (n: number) => Outcome | undefined): {
102
+ kept: Candidate[];
103
+ dropped: number;
104
+ };
105
+ export type { Ask } from '../llm.js';
106
+ /** A request's text in parts of at most `max` characters, split at line ends (a longer line at `max`) — every
107
+ * character in exactly one part. */
108
+ export declare function partsOf(text: string, max?: number): string[];
109
+ export interface ExtractOptions {
110
+ ask?: Ask; /** false: observations are read apart (observeWindow). */
111
+ observations?: boolean;
112
+ /** Passages of other conversations related to this part (consolidate.ts): what a description refers to is named. */
113
+ known?: string[];
114
+ }
115
+ /** Read one window. `unavailable`/`busy` come back as they are, so the worker can pause. */
116
+ export declare function extractWindow(win: ReadableRequest[], meta: {
117
+ cwd: string | null;
118
+ engine: string;
119
+ title: string;
120
+ }, opts?: ExtractOptions): Promise<Extraction | {
121
+ unavailable: string;
122
+ } | {
123
+ busy: true;
124
+ }>;
125
+ /** Observations read apart, Mem0's way: a small window of requests in one focused ask, with what came just before it
126
+ * as context — what a large window's one ask for everything let slip (the events a list question needs). A request
127
+ * longer than the window is read in parts, in order, each part with the one before as its context. */
128
+ export declare function observeWindow(win: ReadableRequest[], before: ReadableRequest[], meta: {
129
+ cwd: string | null;
130
+ engine: string;
131
+ title: string;
132
+ }, opts: {
133
+ ask?: Ask;
134
+ chars: number;
135
+ known?: string[];
136
+ }): Promise<{
137
+ observations: Observation[];
138
+ } | {
139
+ unavailable: string;
140
+ } | {
141
+ busy: true;
142
+ }>;