@loreai/core 0.0.1 → 0.10.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.
Files changed (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -5
  3. package/dist/bun/agents-file.d.ts +59 -0
  4. package/dist/bun/agents-file.d.ts.map +1 -0
  5. package/dist/bun/config.d.ts +58 -0
  6. package/dist/bun/config.d.ts.map +1 -0
  7. package/dist/bun/curator.d.ts +35 -0
  8. package/dist/bun/curator.d.ts.map +1 -0
  9. package/dist/bun/db/driver.bun.d.ts +5 -0
  10. package/dist/bun/db/driver.bun.d.ts.map +1 -0
  11. package/dist/bun/db/driver.node.d.ts +15 -0
  12. package/dist/bun/db/driver.node.d.ts.map +1 -0
  13. package/dist/bun/db.d.ts +22 -0
  14. package/dist/bun/db.d.ts.map +1 -0
  15. package/dist/bun/distillation.d.ts +32 -0
  16. package/dist/bun/distillation.d.ts.map +1 -0
  17. package/dist/bun/embedding.d.ts +90 -0
  18. package/dist/bun/embedding.d.ts.map +1 -0
  19. package/dist/bun/gradient.d.ts +73 -0
  20. package/dist/bun/gradient.d.ts.map +1 -0
  21. package/dist/bun/index.d.ts +19 -0
  22. package/dist/bun/index.d.ts.map +1 -0
  23. package/dist/bun/index.js +28236 -0
  24. package/dist/bun/index.js.map +7 -0
  25. package/dist/bun/lat-reader.d.ts +69 -0
  26. package/dist/bun/lat-reader.d.ts.map +1 -0
  27. package/dist/bun/log.d.ts +17 -0
  28. package/dist/bun/log.d.ts.map +1 -0
  29. package/dist/bun/ltm.d.ts +138 -0
  30. package/dist/bun/ltm.d.ts.map +1 -0
  31. package/dist/bun/markdown.d.ts +37 -0
  32. package/dist/bun/markdown.d.ts.map +1 -0
  33. package/dist/bun/prompt.d.ts +47 -0
  34. package/dist/bun/prompt.d.ts.map +1 -0
  35. package/dist/bun/recall.d.ts +41 -0
  36. package/dist/bun/recall.d.ts.map +1 -0
  37. package/dist/bun/search.d.ts +113 -0
  38. package/dist/bun/search.d.ts.map +1 -0
  39. package/dist/bun/temporal.d.ts +66 -0
  40. package/dist/bun/temporal.d.ts.map +1 -0
  41. package/dist/bun/types.d.ts +180 -0
  42. package/dist/bun/types.d.ts.map +1 -0
  43. package/dist/bun/worker.d.ts +6 -0
  44. package/dist/bun/worker.d.ts.map +1 -0
  45. package/dist/node/agents-file.d.ts +59 -0
  46. package/dist/node/agents-file.d.ts.map +1 -0
  47. package/dist/node/config.d.ts +58 -0
  48. package/dist/node/config.d.ts.map +1 -0
  49. package/dist/node/curator.d.ts +35 -0
  50. package/dist/node/curator.d.ts.map +1 -0
  51. package/dist/node/db/driver.bun.d.ts +5 -0
  52. package/dist/node/db/driver.bun.d.ts.map +1 -0
  53. package/dist/node/db/driver.node.d.ts +15 -0
  54. package/dist/node/db/driver.node.d.ts.map +1 -0
  55. package/dist/node/db.d.ts +22 -0
  56. package/dist/node/db.d.ts.map +1 -0
  57. package/dist/node/distillation.d.ts +32 -0
  58. package/dist/node/distillation.d.ts.map +1 -0
  59. package/dist/node/embedding.d.ts +90 -0
  60. package/dist/node/embedding.d.ts.map +1 -0
  61. package/dist/node/gradient.d.ts +73 -0
  62. package/dist/node/gradient.d.ts.map +1 -0
  63. package/dist/node/index.d.ts +19 -0
  64. package/dist/node/index.d.ts.map +1 -0
  65. package/dist/node/index.js +28253 -0
  66. package/dist/node/index.js.map +7 -0
  67. package/dist/node/lat-reader.d.ts +69 -0
  68. package/dist/node/lat-reader.d.ts.map +1 -0
  69. package/dist/node/log.d.ts +17 -0
  70. package/dist/node/log.d.ts.map +1 -0
  71. package/dist/node/ltm.d.ts +138 -0
  72. package/dist/node/ltm.d.ts.map +1 -0
  73. package/dist/node/markdown.d.ts +37 -0
  74. package/dist/node/markdown.d.ts.map +1 -0
  75. package/dist/node/prompt.d.ts +47 -0
  76. package/dist/node/prompt.d.ts.map +1 -0
  77. package/dist/node/recall.d.ts +41 -0
  78. package/dist/node/recall.d.ts.map +1 -0
  79. package/dist/node/search.d.ts +113 -0
  80. package/dist/node/search.d.ts.map +1 -0
  81. package/dist/node/temporal.d.ts +66 -0
  82. package/dist/node/temporal.d.ts.map +1 -0
  83. package/dist/node/types.d.ts +180 -0
  84. package/dist/node/types.d.ts.map +1 -0
  85. package/dist/node/worker.d.ts +6 -0
  86. package/dist/node/worker.d.ts.map +1 -0
  87. package/dist/types/agents-file.d.ts +59 -0
  88. package/dist/types/agents-file.d.ts.map +1 -0
  89. package/dist/types/config.d.ts +58 -0
  90. package/dist/types/config.d.ts.map +1 -0
  91. package/dist/types/curator.d.ts +35 -0
  92. package/dist/types/curator.d.ts.map +1 -0
  93. package/dist/types/db/driver.bun.d.ts +5 -0
  94. package/dist/types/db/driver.bun.d.ts.map +1 -0
  95. package/dist/types/db/driver.node.d.ts +15 -0
  96. package/dist/types/db/driver.node.d.ts.map +1 -0
  97. package/dist/types/db.d.ts +22 -0
  98. package/dist/types/db.d.ts.map +1 -0
  99. package/dist/types/distillation.d.ts +32 -0
  100. package/dist/types/distillation.d.ts.map +1 -0
  101. package/dist/types/embedding.d.ts +90 -0
  102. package/dist/types/embedding.d.ts.map +1 -0
  103. package/dist/types/gradient.d.ts +73 -0
  104. package/dist/types/gradient.d.ts.map +1 -0
  105. package/dist/types/index.d.ts +19 -0
  106. package/dist/types/index.d.ts.map +1 -0
  107. package/dist/types/lat-reader.d.ts +69 -0
  108. package/dist/types/lat-reader.d.ts.map +1 -0
  109. package/dist/types/log.d.ts +17 -0
  110. package/dist/types/log.d.ts.map +1 -0
  111. package/dist/types/ltm.d.ts +138 -0
  112. package/dist/types/ltm.d.ts.map +1 -0
  113. package/dist/types/markdown.d.ts +37 -0
  114. package/dist/types/markdown.d.ts.map +1 -0
  115. package/dist/types/prompt.d.ts +47 -0
  116. package/dist/types/prompt.d.ts.map +1 -0
  117. package/dist/types/recall.d.ts +41 -0
  118. package/dist/types/recall.d.ts.map +1 -0
  119. package/dist/types/search.d.ts +113 -0
  120. package/dist/types/search.d.ts.map +1 -0
  121. package/dist/types/temporal.d.ts +66 -0
  122. package/dist/types/temporal.d.ts.map +1 -0
  123. package/dist/types/types.d.ts +180 -0
  124. package/dist/types/types.d.ts.map +1 -0
  125. package/dist/types/worker.d.ts +6 -0
  126. package/dist/types/worker.d.ts.map +1 -0
  127. package/package.json +48 -5
  128. package/src/agents-file.ts +406 -0
  129. package/src/config.ts +132 -0
  130. package/src/curator.ts +220 -0
  131. package/src/db/driver.bun.ts +18 -0
  132. package/src/db/driver.node.ts +54 -0
  133. package/src/db.ts +433 -0
  134. package/src/distillation.ts +433 -0
  135. package/src/embedding.ts +528 -0
  136. package/src/gradient.ts +1387 -0
  137. package/src/index.ts +109 -0
  138. package/src/lat-reader.ts +374 -0
  139. package/src/log.ts +27 -0
  140. package/src/ltm.ts +861 -0
  141. package/src/markdown.ts +129 -0
  142. package/src/prompt.ts +454 -0
  143. package/src/recall.ts +446 -0
  144. package/src/search.ts +330 -0
  145. package/src/temporal.ts +379 -0
  146. package/src/types.ts +199 -0
  147. package/src/worker.ts +26 -0
@@ -0,0 +1,433 @@
1
+ import { db, ensureProject } from "./db";
2
+ import { config } from "./config";
3
+ import * as temporal from "./temporal";
4
+ import * as embedding from "./embedding";
5
+ import * as log from "./log";
6
+ import {
7
+ DISTILLATION_SYSTEM,
8
+ distillationUser,
9
+ RECURSIVE_SYSTEM,
10
+ recursiveUser,
11
+ } from "./prompt";
12
+ import { needsUrgentDistillation } from "./gradient";
13
+ import { workerSessionIDs } from "./worker";
14
+ import type { LLMClient } from "./types";
15
+
16
+ // Re-export for backwards compat — index.ts and others may still import from here.
17
+ export { workerSessionIDs };
18
+
19
+ type TemporalMessage = temporal.TemporalMessage;
20
+
21
+ // Segment detection: group related messages together
22
+ function detectSegments(
23
+ messages: TemporalMessage[],
24
+ maxSegment: number,
25
+ ): TemporalMessage[][] {
26
+ if (messages.length <= maxSegment) return [messages];
27
+ const segments: TemporalMessage[][] = [];
28
+ let current: TemporalMessage[] = [];
29
+
30
+ for (const msg of messages) {
31
+ current.push(msg);
32
+ // Split on segment size limit
33
+ if (current.length >= maxSegment) {
34
+ segments.push(current);
35
+ current = [];
36
+ }
37
+ }
38
+ if (current.length > 0) {
39
+ // Merge small trailing segment with previous if too small
40
+ if (current.length < 3 && segments.length > 0) {
41
+ segments[segments.length - 1].push(...current);
42
+ } else {
43
+ segments.push(current);
44
+ }
45
+ }
46
+ return segments;
47
+ }
48
+
49
+ function formatTime(ms: number): string {
50
+ const d = new Date(ms);
51
+ const h = d.getHours().toString().padStart(2, "0");
52
+ const m = d.getMinutes().toString().padStart(2, "0");
53
+ return `${h}:${m}`;
54
+ }
55
+
56
+ function messagesToText(messages: TemporalMessage[]): string {
57
+ return messages
58
+ .map((m) => `[${m.role}] (${formatTime(m.created_at)}) ${m.content}`)
59
+ .join("\n\n");
60
+ }
61
+
62
+ type DistillationResult = {
63
+ observations: string;
64
+ };
65
+
66
+ function parseDistillationResult(text: string): DistillationResult | null {
67
+ // Extract content from <observations>...</observations> block
68
+ const match = text.match(/<observations>([\s\S]*?)<\/observations>/i);
69
+ const observations = match ? match[1].trim() : text.trim();
70
+ if (!observations) return null;
71
+ return { observations };
72
+ }
73
+
74
+ // Get the most recent observations for context
75
+ function latestObservations(
76
+ projectPath: string,
77
+ sessionID: string,
78
+ ): string | undefined {
79
+ const pid = ensureProject(projectPath);
80
+ const row = db()
81
+ .query(
82
+ "SELECT observations FROM distillations WHERE project_id = ? AND session_id = ? ORDER BY created_at DESC LIMIT 1",
83
+ )
84
+ .get(pid, sessionID) as { observations: string } | null;
85
+ return row?.observations || undefined;
86
+ }
87
+
88
+ /** Safely parse the source_ids JSON column. Defaults to [] on corrupt data. */
89
+ export function parseSourceIds(raw: string): string[] {
90
+ try {
91
+ const parsed = JSON.parse(raw);
92
+ return Array.isArray(parsed) ? parsed : [];
93
+ } catch {
94
+ log.warn("corrupt source_ids in distillation, defaulting to []");
95
+ return [];
96
+ }
97
+ }
98
+
99
+ export type Distillation = {
100
+ id: string;
101
+ project_id: string;
102
+ session_id: string;
103
+ observations: string;
104
+ source_ids: string[];
105
+ generation: number;
106
+ token_count: number;
107
+ created_at: number;
108
+ };
109
+
110
+ /** Load all distillations for a session, oldest first. */
111
+ export function loadForSession(
112
+ projectPath: string,
113
+ sessionID: string,
114
+ ): Distillation[] {
115
+ const pid = ensureProject(projectPath);
116
+ const rows = db()
117
+ .query(
118
+ "SELECT id, project_id, session_id, observations, source_ids, generation, token_count, created_at FROM distillations WHERE project_id = ? AND session_id = ? ORDER BY created_at ASC",
119
+ )
120
+ .all(pid, sessionID) as Array<{
121
+ id: string;
122
+ project_id: string;
123
+ session_id: string;
124
+ observations: string;
125
+ source_ids: string;
126
+ generation: number;
127
+ token_count: number;
128
+ created_at: number;
129
+ }>;
130
+ return rows.map((r) => ({
131
+ ...r,
132
+ source_ids: parseSourceIds(r.source_ids),
133
+ }));
134
+ }
135
+
136
+ function storeDistillation(input: {
137
+ projectPath: string;
138
+ sessionID: string;
139
+ observations: string;
140
+ sourceIDs: string[];
141
+ generation: number;
142
+ }): string {
143
+ const pid = ensureProject(input.projectPath);
144
+ const id = crypto.randomUUID();
145
+ const sourceJson = JSON.stringify(input.sourceIDs);
146
+ const tokens = Math.ceil(input.observations.length / 3);
147
+ db()
148
+ .query(
149
+ `INSERT INTO distillations (id, project_id, session_id, narrative, facts, observations, source_ids, generation, token_count, created_at)
150
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
151
+ )
152
+ .run(
153
+ id,
154
+ pid,
155
+ input.sessionID,
156
+ "", // legacy column — kept for schema compat
157
+ "[]", // legacy column — kept for schema compat
158
+ input.observations,
159
+ sourceJson,
160
+ input.generation,
161
+ tokens,
162
+ Date.now(),
163
+ );
164
+ return id;
165
+ }
166
+
167
+ // Count non-archived gen-0 distillations — these are the ones awaiting
168
+ // meta-distillation. Archived gen-0 entries have already been consolidated.
169
+ function gen0Count(projectPath: string, sessionID: string): number {
170
+ const pid = ensureProject(projectPath);
171
+ return (
172
+ db()
173
+ .query(
174
+ "SELECT COUNT(*) as count FROM distillations WHERE project_id = ? AND session_id = ? AND generation = 0 AND archived = 0",
175
+ )
176
+ .get(pid, sessionID) as { count: number }
177
+ ).count;
178
+ }
179
+
180
+ // Load non-archived gen-0 distillations for meta-distillation input.
181
+ function loadGen0(projectPath: string, sessionID: string): Distillation[] {
182
+ const pid = ensureProject(projectPath);
183
+ const rows = db()
184
+ .query(
185
+ "SELECT id, project_id, session_id, observations, source_ids, generation, token_count, created_at FROM distillations WHERE project_id = ? AND session_id = ? AND generation = 0 AND archived = 0 ORDER BY created_at ASC",
186
+ )
187
+ .all(pid, sessionID) as Array<{
188
+ id: string;
189
+ project_id: string;
190
+ session_id: string;
191
+ observations: string;
192
+ source_ids: string;
193
+ generation: number;
194
+ token_count: number;
195
+ created_at: number;
196
+ }>;
197
+ return rows.map((r) => ({
198
+ ...r,
199
+ source_ids: parseSourceIds(r.source_ids),
200
+ }));
201
+ }
202
+
203
+ // Archive distillations instead of deleting them. Archived entries are excluded
204
+ // from the in-context prefix (loadDistillations filters them out) but remain
205
+ // searchable via the recall tool (searchDistillations includes them). This
206
+ // preserves a detailed "zoom-in" layer beneath the compressed gen-1 summary.
207
+ // Inspired by Cartridges (Eyuboglu et al., 2025): independently compressed
208
+ // representations remain composable and queryable after consolidation.
209
+ // Reference: https://arxiv.org/abs/2501.17390
210
+ function archiveDistillations(ids: string[]) {
211
+ if (!ids.length) return;
212
+ const placeholders = ids.map(() => "?").join(",");
213
+ db()
214
+ .query(
215
+ `UPDATE distillations SET archived = 1 WHERE id IN (${placeholders})`,
216
+ )
217
+ .run(...ids);
218
+ }
219
+
220
+ // Reset messages that were marked distilled by a previous format/run but aren't
221
+ // covered by any current distillation. This happens when distillations are deleted
222
+ // (e.g., format migration from v1 to v2) but the temporal messages keep distilled=1.
223
+ function resetOrphans(projectPath: string, sessionID: string): number {
224
+ const pid = ensureProject(projectPath);
225
+ // Collect all message IDs referenced by existing distillations
226
+ const rows = db()
227
+ .query(
228
+ "SELECT source_ids FROM distillations WHERE project_id = ? AND session_id = ?",
229
+ )
230
+ .all(pid, sessionID) as Array<{ source_ids: string }>;
231
+ const covered = new Set<string>();
232
+ for (const r of rows) {
233
+ for (const id of parseSourceIds(r.source_ids)) covered.add(id);
234
+ }
235
+ if (rows.length === 0) {
236
+ // No distillations at all — reset everything to undistilled
237
+ const result = db()
238
+ .query(
239
+ "UPDATE temporal_messages SET distilled = 0 WHERE project_id = ? AND session_id = ? AND distilled = 1",
240
+ )
241
+ .run(pid, sessionID);
242
+ // node:sqlite returns `changes` as `number | bigint`; bun:sqlite returns `number`.
243
+ // Coerce to number — SQLite will never return a row count > 2^53.
244
+ return Number(result.changes);
245
+ }
246
+ // Find orphans: marked distilled but not in any source_ids
247
+ const distilled = db()
248
+ .query(
249
+ "SELECT id FROM temporal_messages WHERE project_id = ? AND session_id = ? AND distilled = 1",
250
+ )
251
+ .all(pid, sessionID) as Array<{ id: string }>;
252
+ const orphans = distilled.filter((m) => !covered.has(m.id)).map((m) => m.id);
253
+ if (!orphans.length) return 0;
254
+ // Reset in batches to avoid SQLite parameter limit
255
+ const batch = 500;
256
+ for (let i = 0; i < orphans.length; i += batch) {
257
+ const chunk = orphans.slice(i, i + batch);
258
+ const placeholders = chunk.map(() => "?").join(",");
259
+ db()
260
+ .query(
261
+ `UPDATE temporal_messages SET distilled = 0 WHERE id IN (${placeholders})`,
262
+ )
263
+ .run(...chunk);
264
+ }
265
+ return orphans.length;
266
+ }
267
+
268
+ // Main distillation entry point — called on session.idle or when urgent
269
+ export async function run(input: {
270
+ llm: LLMClient;
271
+ projectPath: string;
272
+ sessionID: string;
273
+ model?: { providerID: string; modelID: string };
274
+ /** Skip minMessages threshold check — distill whatever is pending */
275
+ force?: boolean;
276
+ }): Promise<{ rounds: number; distilled: number }> {
277
+ // Reset orphaned messages (marked distilled by a deleted/migrated distillation)
278
+ const orphans = resetOrphans(input.projectPath, input.sessionID);
279
+ if (orphans > 0) {
280
+ log.info(
281
+ `Reset ${orphans} orphaned messages for re-observation`,
282
+ );
283
+ }
284
+
285
+ const cfg = config();
286
+ const maxRounds = 3;
287
+ let rounds = 0;
288
+ let distilled = 0;
289
+
290
+ for (let round = 0; round < maxRounds; round++) {
291
+ // Check if there are enough undistilled messages
292
+ const pending = temporal.undistilled(input.projectPath, input.sessionID);
293
+ if (
294
+ !input.force &&
295
+ pending.length < cfg.distillation.minMessages &&
296
+ round === 0
297
+ )
298
+ break;
299
+
300
+ if (pending.length > 0) {
301
+ const segments = detectSegments(pending, cfg.distillation.maxSegment);
302
+ for (const segment of segments) {
303
+ const result = await distillSegment({
304
+ llm: input.llm,
305
+ projectPath: input.projectPath,
306
+ sessionID: input.sessionID,
307
+ messages: segment,
308
+ model: input.model,
309
+ });
310
+ if (result) {
311
+ distilled += segment.length;
312
+ rounds++;
313
+ }
314
+ }
315
+ }
316
+
317
+ // Check if meta-distillation is needed
318
+ if (
319
+ gen0Count(input.projectPath, input.sessionID) >=
320
+ cfg.distillation.metaThreshold
321
+ ) {
322
+ await metaDistill({
323
+ llm: input.llm,
324
+ projectPath: input.projectPath,
325
+ sessionID: input.sessionID,
326
+ model: input.model,
327
+ });
328
+ rounds++;
329
+ }
330
+
331
+ // Check if we still need urgent distillation
332
+ if (!needsUrgentDistillation()) break;
333
+ }
334
+
335
+ return { rounds, distilled };
336
+ }
337
+
338
+ async function distillSegment(input: {
339
+ llm: LLMClient;
340
+ projectPath: string;
341
+ sessionID: string;
342
+ messages: TemporalMessage[];
343
+ model?: { providerID: string; modelID: string };
344
+ }): Promise<DistillationResult | null> {
345
+ const prior = latestObservations(input.projectPath, input.sessionID);
346
+ const text = messagesToText(input.messages);
347
+ // Derive session date from first message timestamp
348
+ const first = input.messages[0];
349
+ const date = first
350
+ ? new Date(first.created_at).toLocaleDateString("en-US", {
351
+ year: "numeric",
352
+ month: "long",
353
+ day: "numeric",
354
+ })
355
+ : "unknown date";
356
+ const userContent = distillationUser({
357
+ priorObservations: prior,
358
+ date,
359
+ messages: text,
360
+ });
361
+
362
+ const model = input.model ?? config().model;
363
+ const responseText = await input.llm.prompt(
364
+ DISTILLATION_SYSTEM,
365
+ userContent,
366
+ { model, workerID: "lore-distill" },
367
+ );
368
+ if (!responseText) return null;
369
+
370
+ const result = parseDistillationResult(responseText);
371
+ if (!result) return null;
372
+
373
+ const distillId = storeDistillation({
374
+ projectPath: input.projectPath,
375
+ sessionID: input.sessionID,
376
+ observations: result.observations,
377
+ sourceIDs: input.messages.map((m) => m.id),
378
+ generation: 0,
379
+ });
380
+ temporal.markDistilled(input.messages.map((m) => m.id));
381
+
382
+ // Fire-and-forget: embed the distillation for vector search
383
+ if (embedding.isAvailable()) {
384
+ embedding.embedDistillation(distillId, result.observations);
385
+ }
386
+
387
+ return result;
388
+ }
389
+
390
+ async function metaDistill(input: {
391
+ llm: LLMClient;
392
+ projectPath: string;
393
+ sessionID: string;
394
+ model?: { providerID: string; modelID: string };
395
+ }): Promise<DistillationResult | null> {
396
+ const existing = loadGen0(input.projectPath, input.sessionID);
397
+ if (existing.length < 3) return null;
398
+
399
+ const userContent = recursiveUser(existing);
400
+
401
+ const model = input.model ?? config().model;
402
+ const responseText = await input.llm.prompt(
403
+ RECURSIVE_SYSTEM,
404
+ userContent,
405
+ { model, workerID: "lore-distill" },
406
+ );
407
+ if (!responseText) return null;
408
+
409
+ const result = parseDistillationResult(responseText);
410
+ if (!result) return null;
411
+
412
+ // Store the meta-distillation at generation N+1
413
+ const maxGen = Math.max(...existing.map((d) => d.generation));
414
+ const allSourceIDs = existing.flatMap((d) => d.source_ids);
415
+ const metaId = storeDistillation({
416
+ projectPath: input.projectPath,
417
+ sessionID: input.sessionID,
418
+ observations: result.observations,
419
+ sourceIDs: allSourceIDs,
420
+ generation: maxGen + 1,
421
+ });
422
+
423
+ // Fire-and-forget: embed the meta-distillation for vector search
424
+ if (embedding.isAvailable()) {
425
+ embedding.embedDistillation(metaId, result.observations);
426
+ }
427
+
428
+ // Archive the gen-0 distillations that were merged into gen-1+.
429
+ // They remain searchable via recall but excluded from the in-context prefix.
430
+ archiveDistillations(existing.map((d) => d.id));
431
+
432
+ return result;
433
+ }