@loreai/core 0.0.1 → 0.10.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.
- package/LICENSE +21 -0
- package/README.md +26 -5
- package/dist/bun/agents-file.d.ts +59 -0
- package/dist/bun/agents-file.d.ts.map +1 -0
- package/dist/bun/config.d.ts +58 -0
- package/dist/bun/config.d.ts.map +1 -0
- package/dist/bun/curator.d.ts +35 -0
- package/dist/bun/curator.d.ts.map +1 -0
- package/dist/bun/db/driver.bun.d.ts +5 -0
- package/dist/bun/db/driver.bun.d.ts.map +1 -0
- package/dist/bun/db/driver.node.d.ts +15 -0
- package/dist/bun/db/driver.node.d.ts.map +1 -0
- package/dist/bun/db.d.ts +22 -0
- package/dist/bun/db.d.ts.map +1 -0
- package/dist/bun/distillation.d.ts +32 -0
- package/dist/bun/distillation.d.ts.map +1 -0
- package/dist/bun/embedding.d.ts +90 -0
- package/dist/bun/embedding.d.ts.map +1 -0
- package/dist/bun/gradient.d.ts +73 -0
- package/dist/bun/gradient.d.ts.map +1 -0
- package/dist/bun/index.d.ts +19 -0
- package/dist/bun/index.d.ts.map +1 -0
- package/dist/bun/index.js +28236 -0
- package/dist/bun/index.js.map +7 -0
- package/dist/bun/lat-reader.d.ts +69 -0
- package/dist/bun/lat-reader.d.ts.map +1 -0
- package/dist/bun/log.d.ts +17 -0
- package/dist/bun/log.d.ts.map +1 -0
- package/dist/bun/ltm.d.ts +138 -0
- package/dist/bun/ltm.d.ts.map +1 -0
- package/dist/bun/markdown.d.ts +37 -0
- package/dist/bun/markdown.d.ts.map +1 -0
- package/dist/bun/prompt.d.ts +47 -0
- package/dist/bun/prompt.d.ts.map +1 -0
- package/dist/bun/recall.d.ts +41 -0
- package/dist/bun/recall.d.ts.map +1 -0
- package/dist/bun/search.d.ts +113 -0
- package/dist/bun/search.d.ts.map +1 -0
- package/dist/bun/temporal.d.ts +66 -0
- package/dist/bun/temporal.d.ts.map +1 -0
- package/dist/bun/types.d.ts +180 -0
- package/dist/bun/types.d.ts.map +1 -0
- package/dist/bun/worker.d.ts +6 -0
- package/dist/bun/worker.d.ts.map +1 -0
- package/dist/node/agents-file.d.ts +59 -0
- package/dist/node/agents-file.d.ts.map +1 -0
- package/dist/node/config.d.ts +58 -0
- package/dist/node/config.d.ts.map +1 -0
- package/dist/node/curator.d.ts +35 -0
- package/dist/node/curator.d.ts.map +1 -0
- package/dist/node/db/driver.bun.d.ts +5 -0
- package/dist/node/db/driver.bun.d.ts.map +1 -0
- package/dist/node/db/driver.node.d.ts +15 -0
- package/dist/node/db/driver.node.d.ts.map +1 -0
- package/dist/node/db.d.ts +22 -0
- package/dist/node/db.d.ts.map +1 -0
- package/dist/node/distillation.d.ts +32 -0
- package/dist/node/distillation.d.ts.map +1 -0
- package/dist/node/embedding.d.ts +90 -0
- package/dist/node/embedding.d.ts.map +1 -0
- package/dist/node/gradient.d.ts +73 -0
- package/dist/node/gradient.d.ts.map +1 -0
- package/dist/node/index.d.ts +19 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +28253 -0
- package/dist/node/index.js.map +7 -0
- package/dist/node/lat-reader.d.ts +69 -0
- package/dist/node/lat-reader.d.ts.map +1 -0
- package/dist/node/log.d.ts +17 -0
- package/dist/node/log.d.ts.map +1 -0
- package/dist/node/ltm.d.ts +138 -0
- package/dist/node/ltm.d.ts.map +1 -0
- package/dist/node/markdown.d.ts +37 -0
- package/dist/node/markdown.d.ts.map +1 -0
- package/dist/node/prompt.d.ts +47 -0
- package/dist/node/prompt.d.ts.map +1 -0
- package/dist/node/recall.d.ts +41 -0
- package/dist/node/recall.d.ts.map +1 -0
- package/dist/node/search.d.ts +113 -0
- package/dist/node/search.d.ts.map +1 -0
- package/dist/node/temporal.d.ts +66 -0
- package/dist/node/temporal.d.ts.map +1 -0
- package/dist/node/types.d.ts +180 -0
- package/dist/node/types.d.ts.map +1 -0
- package/dist/node/worker.d.ts +6 -0
- package/dist/node/worker.d.ts.map +1 -0
- package/dist/types/agents-file.d.ts +59 -0
- package/dist/types/agents-file.d.ts.map +1 -0
- package/dist/types/config.d.ts +58 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/curator.d.ts +35 -0
- package/dist/types/curator.d.ts.map +1 -0
- package/dist/types/db/driver.bun.d.ts +5 -0
- package/dist/types/db/driver.bun.d.ts.map +1 -0
- package/dist/types/db/driver.node.d.ts +15 -0
- package/dist/types/db/driver.node.d.ts.map +1 -0
- package/dist/types/db.d.ts +22 -0
- package/dist/types/db.d.ts.map +1 -0
- package/dist/types/distillation.d.ts +32 -0
- package/dist/types/distillation.d.ts.map +1 -0
- package/dist/types/embedding.d.ts +90 -0
- package/dist/types/embedding.d.ts.map +1 -0
- package/dist/types/gradient.d.ts +73 -0
- package/dist/types/gradient.d.ts.map +1 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/lat-reader.d.ts +69 -0
- package/dist/types/lat-reader.d.ts.map +1 -0
- package/dist/types/log.d.ts +17 -0
- package/dist/types/log.d.ts.map +1 -0
- package/dist/types/ltm.d.ts +138 -0
- package/dist/types/ltm.d.ts.map +1 -0
- package/dist/types/markdown.d.ts +37 -0
- package/dist/types/markdown.d.ts.map +1 -0
- package/dist/types/prompt.d.ts +47 -0
- package/dist/types/prompt.d.ts.map +1 -0
- package/dist/types/recall.d.ts +41 -0
- package/dist/types/recall.d.ts.map +1 -0
- package/dist/types/search.d.ts +113 -0
- package/dist/types/search.d.ts.map +1 -0
- package/dist/types/temporal.d.ts +66 -0
- package/dist/types/temporal.d.ts.map +1 -0
- package/dist/types/types.d.ts +180 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/worker.d.ts +6 -0
- package/dist/types/worker.d.ts.map +1 -0
- package/package.json +48 -5
- package/src/agents-file.ts +406 -0
- package/src/config.ts +132 -0
- package/src/curator.ts +220 -0
- package/src/db/driver.bun.ts +18 -0
- package/src/db/driver.node.ts +54 -0
- package/src/db.ts +433 -0
- package/src/distillation.ts +433 -0
- package/src/embedding.ts +528 -0
- package/src/gradient.ts +1387 -0
- package/src/index.ts +109 -0
- package/src/lat-reader.ts +374 -0
- package/src/log.ts +27 -0
- package/src/ltm.ts +861 -0
- package/src/markdown.ts +129 -0
- package/src/prompt.ts +454 -0
- package/src/recall.ts +446 -0
- package/src/search.ts +330 -0
- package/src/temporal.ts +379 -0
- package/src/types.ts +199 -0
- 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
|
+
}
|