kongbrain 0.1.1 → 0.1.3
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/README.md +6 -5
- package/package.json +1 -1
- package/src/context-engine.ts +16 -8
- package/src/daemon-manager.ts +183 -99
- package/src/daemon-types.ts +1 -48
- package/src/deferred-cleanup.ts +141 -0
- package/src/handoff-file.ts +55 -0
- package/src/hooks/llm-output.ts +12 -91
- package/src/index.ts +90 -18
- package/src/memory-daemon.ts +181 -376
- package/src/reflection.ts +1 -1
- package/src/schema.surql +2 -0
- package/src/state.ts +6 -1
- package/src/surreal.ts +24 -0
- package/src/wakeup.ts +13 -1
package/src/memory-daemon.ts
CHANGED
|
@@ -1,67 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Memory Daemon —
|
|
2
|
+
* Memory Daemon — extraction logic for incremental knowledge extraction.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Contains the prompt building, transcript formatting, and DB write logic
|
|
5
|
+
* used by the daemon manager to extract 9 knowledge types from conversation
|
|
6
|
+
* turns: causal chains, monologue traces, resolved memories, concepts,
|
|
7
|
+
* corrections, preferences, artifacts, decisions, skills.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
* concepts, corrections, preferences, artifacts, decisions, skills.
|
|
10
|
-
*
|
|
11
|
-
* This file runs inside a Worker thread — it is NOT imported by the main thread.
|
|
12
|
-
*
|
|
13
|
-
* Ported from kongbrain — creates own SurrealStore/EmbeddingService instances.
|
|
9
|
+
* Ported from kongbrain — takes SurrealStore/EmbeddingService as params.
|
|
14
10
|
*/
|
|
15
|
-
import {
|
|
16
|
-
import type {
|
|
17
|
-
import {
|
|
18
|
-
import { EmbeddingService } from "./embeddings.js";
|
|
11
|
+
import type { TurnData, PriorExtractions } from "./daemon-types.js";
|
|
12
|
+
import type { SurrealStore } from "./surreal.js";
|
|
13
|
+
import type { EmbeddingService } from "./embeddings.js";
|
|
19
14
|
import { swallow } from "./errors.js";
|
|
20
15
|
|
|
21
|
-
if (!parentPort) {
|
|
22
|
-
throw new Error("memory-daemon.ts must be run as a worker thread");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const config = workerData as DaemonWorkerData;
|
|
26
|
-
|
|
27
|
-
// Worker-local instances
|
|
28
|
-
let store: SurrealStore;
|
|
29
|
-
let embeddings: EmbeddingService;
|
|
30
|
-
|
|
31
|
-
// --- Cumulative extraction counts ---
|
|
32
|
-
const counts = {
|
|
33
|
-
turns: 0, causal: 0, monologue: 0, resolved: 0, concept: 0,
|
|
34
|
-
correction: 0, preference: 0, artifact: 0, decision: 0, skill: 0, errors: 0,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
let processing = false;
|
|
38
|
-
let shuttingDown = false;
|
|
39
|
-
const batchQueue: DaemonMessage[] = [];
|
|
40
|
-
|
|
41
|
-
const priorState: PriorExtractions = {
|
|
42
|
-
conceptNames: [], artifactPaths: [], skillNames: [],
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// --- Initialization ---
|
|
46
|
-
|
|
47
|
-
async function init(): Promise<boolean> {
|
|
48
|
-
try {
|
|
49
|
-
store = new SurrealStore(config.surrealConfig);
|
|
50
|
-
await store.initialize();
|
|
51
|
-
|
|
52
|
-
embeddings = new EmbeddingService(config.embeddingConfig);
|
|
53
|
-
await embeddings.initialize();
|
|
54
|
-
|
|
55
|
-
return true;
|
|
56
|
-
} catch (e) {
|
|
57
|
-
swallow.warn("memory-daemon:init", e);
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
16
|
// --- Build the extraction prompt ---
|
|
63
17
|
|
|
64
|
-
function buildSystemPrompt(
|
|
18
|
+
export function buildSystemPrompt(
|
|
65
19
|
hasThinking: boolean,
|
|
66
20
|
hasRetrievedMemories: boolean,
|
|
67
21
|
prior: PriorExtractions,
|
|
@@ -136,7 +90,7 @@ RULES:
|
|
|
136
90
|
- For artifacts, extract file paths from bash/tool commands in the transcript.`;
|
|
137
91
|
}
|
|
138
92
|
|
|
139
|
-
function buildTranscript(turns: TurnData[]): string {
|
|
93
|
+
export function buildTranscript(turns: TurnData[]): string {
|
|
140
94
|
return turns
|
|
141
95
|
.map(t => {
|
|
142
96
|
const prefix = t.tool_name ? `[tool:${t.tool_name}]` : `[${t.role}]`;
|
|
@@ -148,348 +102,199 @@ function buildTranscript(turns: TurnData[]): string {
|
|
|
148
102
|
.join("\n");
|
|
149
103
|
}
|
|
150
104
|
|
|
151
|
-
// ---
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
for (const path of priorExtractions.artifactPaths) {
|
|
166
|
-
if (!priorState.artifactPaths.includes(path)) priorState.artifactPaths.push(path);
|
|
167
|
-
}
|
|
168
|
-
for (const name of priorExtractions.skillNames) {
|
|
169
|
-
if (!priorState.skillNames.includes(name)) priorState.skillNames.push(name);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const transcript = buildTranscript(turns);
|
|
174
|
-
const sections: string[] = [`[TRANSCRIPT]\n${transcript.slice(0, 60000)}`];
|
|
175
|
-
|
|
176
|
-
if (thinking.length > 0) {
|
|
177
|
-
sections.push(`[THINKING]\n${thinking.slice(-8).join("\n---\n").slice(0, 4000)}`);
|
|
178
|
-
}
|
|
105
|
+
// --- Write extraction results to DB ---
|
|
106
|
+
|
|
107
|
+
export interface ExtractionCounts {
|
|
108
|
+
causal: number;
|
|
109
|
+
monologue: number;
|
|
110
|
+
resolved: number;
|
|
111
|
+
concept: number;
|
|
112
|
+
correction: number;
|
|
113
|
+
preference: number;
|
|
114
|
+
artifact: number;
|
|
115
|
+
decision: number;
|
|
116
|
+
skill: number;
|
|
117
|
+
}
|
|
179
118
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
119
|
+
export async function writeExtractionResults(
|
|
120
|
+
result: Record<string, any>,
|
|
121
|
+
sessionId: string,
|
|
122
|
+
store: SurrealStore,
|
|
123
|
+
embeddings: EmbeddingService,
|
|
124
|
+
priorState: PriorExtractions,
|
|
125
|
+
): Promise<ExtractionCounts> {
|
|
126
|
+
const counts: ExtractionCounts = {
|
|
127
|
+
causal: 0, monologue: 0, resolved: 0, concept: 0,
|
|
128
|
+
correction: 0, preference: 0, artifact: 0, decision: 0, skill: 0,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const writeOps: Promise<void>[] = [];
|
|
132
|
+
|
|
133
|
+
// 1. Causal chains
|
|
134
|
+
if (Array.isArray(result.causal) && result.causal.length > 0) {
|
|
135
|
+
const { linkCausalEdges } = await import("./causal.js");
|
|
136
|
+
const validated = result.causal
|
|
137
|
+
.filter((c: any) => c.triggerText && c.outcomeText && c.chainType && typeof c.success === "boolean")
|
|
138
|
+
.slice(0, 5)
|
|
139
|
+
.map((c: any) => ({
|
|
140
|
+
triggerText: String(c.triggerText).slice(0, 200),
|
|
141
|
+
outcomeText: String(c.outcomeText).slice(0, 200),
|
|
142
|
+
chainType: (["debug", "refactor", "feature", "fix"].includes(c.chainType) ? c.chainType : "fix") as "debug" | "refactor" | "feature" | "fix",
|
|
143
|
+
success: Boolean(c.success),
|
|
144
|
+
confidence: Math.max(0, Math.min(1, Number(c.confidence) || 0.5)),
|
|
145
|
+
description: String(c.description ?? "").slice(0, 150),
|
|
146
|
+
}));
|
|
147
|
+
if (validated.length > 0) {
|
|
148
|
+
writeOps.push(linkCausalEdges(validated, sessionId, store, embeddings));
|
|
149
|
+
counts.causal += validated.length;
|
|
183
150
|
}
|
|
151
|
+
}
|
|
184
152
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
systemPrompt,
|
|
195
|
-
messages: [{
|
|
196
|
-
role: "user",
|
|
197
|
-
timestamp: Date.now(),
|
|
198
|
-
content: sections.join("\n\n"),
|
|
199
|
-
}],
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
const responseText = response.content
|
|
203
|
-
.filter((c: any) => c.type === "text")
|
|
204
|
-
.map((c: any) => c.text)
|
|
205
|
-
.join("");
|
|
206
|
-
|
|
207
|
-
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
|
|
208
|
-
if (!jsonMatch) return;
|
|
209
|
-
|
|
210
|
-
let result: Record<string, any>;
|
|
211
|
-
try {
|
|
212
|
-
result = JSON.parse(jsonMatch[0]);
|
|
213
|
-
} catch {
|
|
214
|
-
try {
|
|
215
|
-
result = JSON.parse(jsonMatch[0].replace(/,\s*([}\]])/g, "$1"));
|
|
216
|
-
} catch {
|
|
217
|
-
// Per-field fallback
|
|
218
|
-
result = {};
|
|
219
|
-
const fields = ["causal", "monologue", "resolved", "concepts", "corrections", "preferences", "artifacts", "decisions", "skills"];
|
|
220
|
-
for (const field of fields) {
|
|
221
|
-
const fieldMatch = jsonMatch[0].match(new RegExp(`"${field}"\\s*:\\s*(\\[[\\s\\S]*?\\])(?=\\s*[,}]\\s*"[a-z]|\\s*\\}$)`, "m"));
|
|
222
|
-
if (fieldMatch) {
|
|
223
|
-
try { result[field] = JSON.parse(fieldMatch[1]); } catch { /* skip */ }
|
|
224
|
-
}
|
|
153
|
+
// 2. Monologue traces
|
|
154
|
+
if (Array.isArray(result.monologue) && result.monologue.length > 0) {
|
|
155
|
+
for (const entry of result.monologue.slice(0, 5)) {
|
|
156
|
+
if (!entry.category || !entry.content) continue;
|
|
157
|
+
counts.monologue++;
|
|
158
|
+
writeOps.push((async () => {
|
|
159
|
+
let emb: number[] | null = null;
|
|
160
|
+
if (embeddings.isAvailable()) {
|
|
161
|
+
try { emb = await embeddings.embed(entry.content); } catch (e) { swallow("daemon:embedMonologue", e); }
|
|
225
162
|
}
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// --- Write all results to DB ---
|
|
231
|
-
const writeOps: Promise<void>[] = [];
|
|
232
|
-
|
|
233
|
-
// 1. Causal chains
|
|
234
|
-
if (Array.isArray(result.causal) && result.causal.length > 0) {
|
|
235
|
-
const { linkCausalEdges } = await import("./causal.js");
|
|
236
|
-
const validated = result.causal
|
|
237
|
-
.filter((c: any) => c.triggerText && c.outcomeText && c.chainType && typeof c.success === "boolean")
|
|
238
|
-
.slice(0, 5)
|
|
239
|
-
.map((c: any) => ({
|
|
240
|
-
triggerText: String(c.triggerText).slice(0, 200),
|
|
241
|
-
outcomeText: String(c.outcomeText).slice(0, 200),
|
|
242
|
-
chainType: (["debug", "refactor", "feature", "fix"].includes(c.chainType) ? c.chainType : "fix") as "debug" | "refactor" | "feature" | "fix",
|
|
243
|
-
success: Boolean(c.success),
|
|
244
|
-
confidence: Math.max(0, Math.min(1, Number(c.confidence) || 0.5)),
|
|
245
|
-
description: String(c.description ?? "").slice(0, 150),
|
|
246
|
-
}));
|
|
247
|
-
if (validated.length > 0) {
|
|
248
|
-
writeOps.push(linkCausalEdges(validated, sessionId, store, embeddings));
|
|
249
|
-
counts.causal += validated.length;
|
|
250
|
-
}
|
|
163
|
+
await store.createMonologue(sessionId, entry.category, entry.content, emb);
|
|
164
|
+
})());
|
|
251
165
|
}
|
|
166
|
+
}
|
|
252
167
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
})());
|
|
168
|
+
// 3. Resolved memories
|
|
169
|
+
if (Array.isArray(result.resolved) && result.resolved.length > 0) {
|
|
170
|
+
const RECORD_ID_RE = /^memory:[a-zA-Z0-9_]+$/;
|
|
171
|
+
writeOps.push((async () => {
|
|
172
|
+
for (const memId of result.resolved!.slice(0, 20)) {
|
|
173
|
+
if (typeof memId !== "string" || !RECORD_ID_RE.test(memId)) continue;
|
|
174
|
+
counts.resolved++;
|
|
175
|
+
await store.queryExec(
|
|
176
|
+
`UPDATE ${memId} SET status = 'resolved', resolved_at = time::now(), resolved_by = $sid`,
|
|
177
|
+
{ sid: sessionId },
|
|
178
|
+
).catch(e => swallow.warn("daemon:resolveMemory", e));
|
|
265
179
|
}
|
|
266
|
-
}
|
|
180
|
+
})());
|
|
181
|
+
}
|
|
267
182
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
183
|
+
// 4. Concepts
|
|
184
|
+
if (Array.isArray(result.concepts) && result.concepts.length > 0) {
|
|
185
|
+
for (const c of result.concepts.slice(0, 11)) {
|
|
186
|
+
if (!c.name || !c.content) continue;
|
|
187
|
+
if (priorState.conceptNames.includes(c.name)) continue;
|
|
188
|
+
counts.concept++;
|
|
189
|
+
priorState.conceptNames.push(c.name);
|
|
271
190
|
writeOps.push((async () => {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
await store.queryExec(
|
|
276
|
-
`UPDATE ${memId} SET status = 'resolved', resolved_at = time::now(), resolved_by = $sid`,
|
|
277
|
-
{ sid: sessionId },
|
|
278
|
-
).catch(e => swallow.warn("daemon:resolveMemory", e));
|
|
191
|
+
let emb: number[] | null = null;
|
|
192
|
+
if (embeddings.isAvailable()) {
|
|
193
|
+
try { emb = await embeddings.embed(c.content); } catch (e) { swallow("daemon:embedConcept", e); }
|
|
279
194
|
}
|
|
195
|
+
await store.upsertConcept(c.content, emb, `daemon:${sessionId}`);
|
|
280
196
|
})());
|
|
281
197
|
}
|
|
198
|
+
}
|
|
282
199
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
})());
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// 5. Corrections — high-importance memories
|
|
301
|
-
if (Array.isArray(result.corrections) && result.corrections.length > 0) {
|
|
302
|
-
for (const c of result.corrections.slice(0, 5)) {
|
|
303
|
-
if (!c.original || !c.correction) continue;
|
|
304
|
-
counts.correction++;
|
|
305
|
-
const text = `[CORRECTION] Original: "${String(c.original).slice(0, 200)}" -> Corrected: "${String(c.correction).slice(0, 200)}" (Context: ${String(c.context ?? "").slice(0, 100)})`;
|
|
306
|
-
writeOps.push((async () => {
|
|
307
|
-
let emb: number[] | null = null;
|
|
308
|
-
if (embeddings.isAvailable()) {
|
|
309
|
-
try { emb = await embeddings.embed(text); } catch (e) { swallow("daemon:embedCorrection", e); }
|
|
310
|
-
}
|
|
311
|
-
await store.createMemory(text, emb, 9, "correction", sessionId);
|
|
312
|
-
})());
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// 6. User preferences
|
|
317
|
-
if (Array.isArray(result.preferences) && result.preferences.length > 0) {
|
|
318
|
-
for (const p of result.preferences.slice(0, 5)) {
|
|
319
|
-
if (!p.preference) continue;
|
|
320
|
-
counts.preference++;
|
|
321
|
-
const text = `[USER PREFERENCE] ${String(p.preference).slice(0, 250)} (Evidence: ${String(p.evidence ?? "").slice(0, 150)})`;
|
|
322
|
-
writeOps.push((async () => {
|
|
323
|
-
let emb: number[] | null = null;
|
|
324
|
-
if (embeddings.isAvailable()) {
|
|
325
|
-
try { emb = await embeddings.embed(text); } catch (e) { swallow("daemon:embedPreference", e); }
|
|
326
|
-
}
|
|
327
|
-
await store.createMemory(text, emb, 7, "preference", sessionId);
|
|
328
|
-
})());
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// 7. Artifacts
|
|
333
|
-
if (Array.isArray(result.artifacts) && result.artifacts.length > 0) {
|
|
334
|
-
for (const a of result.artifacts.slice(0, 10)) {
|
|
335
|
-
if (!a.path) continue;
|
|
336
|
-
if (priorState.artifactPaths.includes(a.path)) continue;
|
|
337
|
-
counts.artifact++;
|
|
338
|
-
priorState.artifactPaths.push(a.path);
|
|
339
|
-
const desc = `${String(a.action ?? "modified")}: ${String(a.summary ?? "").slice(0, 200)}`;
|
|
340
|
-
writeOps.push((async () => {
|
|
341
|
-
let emb: number[] | null = null;
|
|
342
|
-
if (embeddings.isAvailable()) {
|
|
343
|
-
try { emb = await embeddings.embed(`${a.path} ${desc}`); } catch (e) { swallow("daemon:embedArtifact", e); }
|
|
344
|
-
}
|
|
345
|
-
await store.createArtifact(a.path, a.action ?? "modified", desc, emb);
|
|
346
|
-
})());
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// 8. Decisions
|
|
351
|
-
if (Array.isArray(result.decisions) && result.decisions.length > 0) {
|
|
352
|
-
for (const d of result.decisions.slice(0, 6)) {
|
|
353
|
-
if (!d.decision) continue;
|
|
354
|
-
counts.decision++;
|
|
355
|
-
const text = `[DECISION] ${String(d.decision).slice(0, 200)} — Rationale: ${String(d.rationale ?? "").slice(0, 200)} (Alternatives: ${String(d.alternatives_considered ?? "none").slice(0, 100)})`;
|
|
356
|
-
writeOps.push((async () => {
|
|
357
|
-
let emb: number[] | null = null;
|
|
358
|
-
if (embeddings.isAvailable()) {
|
|
359
|
-
try { emb = await embeddings.embed(text); } catch (e) { swallow("daemon:embedDecision", e); }
|
|
360
|
-
}
|
|
361
|
-
await store.createMemory(text, emb, 7, "decision", sessionId);
|
|
362
|
-
})());
|
|
363
|
-
}
|
|
200
|
+
// 5. Corrections — high-importance memories
|
|
201
|
+
if (Array.isArray(result.corrections) && result.corrections.length > 0) {
|
|
202
|
+
for (const c of result.corrections.slice(0, 5)) {
|
|
203
|
+
if (!c.original || !c.correction) continue;
|
|
204
|
+
counts.correction++;
|
|
205
|
+
const text = `[CORRECTION] Original: "${String(c.original).slice(0, 200)}" -> Corrected: "${String(c.correction).slice(0, 200)}" (Context: ${String(c.context ?? "").slice(0, 100)})`;
|
|
206
|
+
writeOps.push((async () => {
|
|
207
|
+
let emb: number[] | null = null;
|
|
208
|
+
if (embeddings.isAvailable()) {
|
|
209
|
+
try { emb = await embeddings.embed(text); } catch (e) { swallow("daemon:embedCorrection", e); }
|
|
210
|
+
}
|
|
211
|
+
await store.createMemory(text, emb, 9, "correction", sessionId);
|
|
212
|
+
})());
|
|
364
213
|
}
|
|
214
|
+
}
|
|
365
215
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
await store.queryExec(
|
|
380
|
-
`CREATE skill CONTENT $record`,
|
|
381
|
-
{
|
|
382
|
-
record: {
|
|
383
|
-
name: String(s.name).slice(0, 100),
|
|
384
|
-
description: content,
|
|
385
|
-
content,
|
|
386
|
-
steps: s.steps.map((st: string) => String(st).slice(0, 200)),
|
|
387
|
-
trigger_context: String(s.trigger_context ?? "").slice(0, 200),
|
|
388
|
-
tags: ["auto-extracted"],
|
|
389
|
-
session_id: sessionId,
|
|
390
|
-
...(emb ? { embedding: emb } : {}),
|
|
391
|
-
},
|
|
392
|
-
},
|
|
393
|
-
).catch(e => swallow.warn("daemon:createSkill", e));
|
|
394
|
-
})());
|
|
395
|
-
}
|
|
216
|
+
// 6. User preferences
|
|
217
|
+
if (Array.isArray(result.preferences) && result.preferences.length > 0) {
|
|
218
|
+
for (const p of result.preferences.slice(0, 5)) {
|
|
219
|
+
if (!p.preference) continue;
|
|
220
|
+
counts.preference++;
|
|
221
|
+
const text = `[USER PREFERENCE] ${String(p.preference).slice(0, 250)} (Evidence: ${String(p.evidence ?? "").slice(0, 150)})`;
|
|
222
|
+
writeOps.push((async () => {
|
|
223
|
+
let emb: number[] | null = null;
|
|
224
|
+
if (embeddings.isAvailable()) {
|
|
225
|
+
try { emb = await embeddings.embed(text); } catch (e) { swallow("daemon:embedPreference", e); }
|
|
226
|
+
}
|
|
227
|
+
await store.createMemory(text, emb, 7, "preference", sessionId);
|
|
228
|
+
})());
|
|
396
229
|
}
|
|
397
|
-
|
|
398
|
-
await Promise.allSettled(writeOps);
|
|
399
|
-
|
|
400
|
-
counts.turns = turns.length;
|
|
401
|
-
|
|
402
|
-
parentPort!.postMessage({
|
|
403
|
-
type: "extraction_complete",
|
|
404
|
-
extractedTurnCount: counts.turns,
|
|
405
|
-
causalCount: counts.causal,
|
|
406
|
-
monologueCount: counts.monologue,
|
|
407
|
-
resolvedCount: counts.resolved,
|
|
408
|
-
conceptCount: counts.concept,
|
|
409
|
-
correctionCount: counts.correction,
|
|
410
|
-
preferenceCount: counts.preference,
|
|
411
|
-
artifactCount: counts.artifact,
|
|
412
|
-
decisionCount: counts.decision,
|
|
413
|
-
skillCount: counts.skill,
|
|
414
|
-
extractedNames: { ...priorState },
|
|
415
|
-
} satisfies DaemonResponse);
|
|
416
|
-
} catch (e) {
|
|
417
|
-
counts.errors++;
|
|
418
|
-
swallow.warn("memory-daemon:extraction", e);
|
|
419
|
-
parentPort!.postMessage({
|
|
420
|
-
type: "error",
|
|
421
|
-
message: String(e),
|
|
422
|
-
} satisfies DaemonResponse);
|
|
423
|
-
} finally {
|
|
424
|
-
processing = false;
|
|
425
230
|
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// --- Batch Queue Processing ---
|
|
429
231
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
232
|
+
// 7. Artifacts
|
|
233
|
+
if (Array.isArray(result.artifacts) && result.artifacts.length > 0) {
|
|
234
|
+
for (const a of result.artifacts.slice(0, 10)) {
|
|
235
|
+
if (!a.path) continue;
|
|
236
|
+
if (priorState.artifactPaths.includes(a.path)) continue;
|
|
237
|
+
counts.artifact++;
|
|
238
|
+
priorState.artifactPaths.push(a.path);
|
|
239
|
+
const desc = `${String(a.action ?? "modified")}: ${String(a.summary ?? "").slice(0, 200)}`;
|
|
240
|
+
writeOps.push((async () => {
|
|
241
|
+
let emb: number[] | null = null;
|
|
242
|
+
if (embeddings.isAvailable()) {
|
|
243
|
+
try { emb = await embeddings.embed(`${a.path} ${desc}`); } catch (e) { swallow("daemon:embedArtifact", e); }
|
|
244
|
+
}
|
|
245
|
+
await store.createArtifact(a.path, a.action ?? "modified", desc, emb);
|
|
246
|
+
})());
|
|
435
247
|
}
|
|
436
248
|
}
|
|
437
|
-
}
|
|
438
249
|
|
|
439
|
-
//
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
250
|
+
// 8. Decisions
|
|
251
|
+
if (Array.isArray(result.decisions) && result.decisions.length > 0) {
|
|
252
|
+
for (const d of result.decisions.slice(0, 6)) {
|
|
253
|
+
if (!d.decision) continue;
|
|
254
|
+
counts.decision++;
|
|
255
|
+
const text = `[DECISION] ${String(d.decision).slice(0, 200)} — Rationale: ${String(d.rationale ?? "").slice(0, 200)} (Alternatives: ${String(d.alternatives_considered ?? "none").slice(0, 100)})`;
|
|
256
|
+
writeOps.push((async () => {
|
|
257
|
+
let emb: number[] | null = null;
|
|
258
|
+
if (embeddings.isAvailable()) {
|
|
259
|
+
try { emb = await embeddings.embed(text); } catch (e) { swallow("daemon:embedDecision", e); }
|
|
260
|
+
}
|
|
261
|
+
await store.createMemory(text, emb, 7, "decision", sessionId);
|
|
262
|
+
})());
|
|
450
263
|
}
|
|
451
|
-
|
|
452
|
-
shuttingDown = true;
|
|
453
|
-
if (processing) {
|
|
454
|
-
await Promise.race([
|
|
455
|
-
new Promise<void>(resolve => {
|
|
456
|
-
const check = setInterval(() => {
|
|
457
|
-
if (!processing) { clearInterval(check); resolve(); }
|
|
458
|
-
}, 100);
|
|
459
|
-
}),
|
|
460
|
-
new Promise<void>(resolve => setTimeout(resolve, 45_000)),
|
|
461
|
-
]);
|
|
462
|
-
}
|
|
463
|
-
try {
|
|
464
|
-
await Promise.allSettled([
|
|
465
|
-
store.dispose(),
|
|
466
|
-
embeddings.dispose(),
|
|
467
|
-
]);
|
|
468
|
-
} catch (e) { swallow("daemon:cleanup", e); }
|
|
264
|
+
}
|
|
469
265
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
266
|
+
// 9. Skills
|
|
267
|
+
if (Array.isArray(result.skills) && result.skills.length > 0) {
|
|
268
|
+
for (const s of result.skills.slice(0, 3)) {
|
|
269
|
+
if (!s.name || !Array.isArray(s.steps) || s.steps.length === 0) continue;
|
|
270
|
+
if (priorState.skillNames.includes(s.name)) continue;
|
|
271
|
+
counts.skill++;
|
|
272
|
+
priorState.skillNames.push(s.name);
|
|
273
|
+
const content = `${s.name}\nTrigger: ${String(s.trigger_context ?? "").slice(0, 150)}\nSteps:\n${s.steps.map((st: string, i: number) => `${i + 1}. ${String(st).slice(0, 200)}`).join("\n")}`;
|
|
274
|
+
writeOps.push((async () => {
|
|
275
|
+
let emb: number[] | null = null;
|
|
276
|
+
if (embeddings.isAvailable()) {
|
|
277
|
+
try { emb = await embeddings.embed(content); } catch (e) { swallow("daemon:embedSkill", e); }
|
|
278
|
+
}
|
|
279
|
+
await store.queryExec(
|
|
280
|
+
`CREATE skill CONTENT $record`,
|
|
281
|
+
{
|
|
282
|
+
record: {
|
|
283
|
+
name: String(s.name).slice(0, 100),
|
|
284
|
+
description: content,
|
|
285
|
+
content,
|
|
286
|
+
steps: s.steps.map((st: string) => String(st).slice(0, 200)),
|
|
287
|
+
trigger_context: String(s.trigger_context ?? "").slice(0, 200),
|
|
288
|
+
tags: ["auto-extracted"],
|
|
289
|
+
session_id: sessionId,
|
|
290
|
+
...(emb ? { embedding: emb } : {}),
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
).catch(e => swallow.warn("daemon:createSkill", e));
|
|
294
|
+
})());
|
|
481
295
|
}
|
|
482
296
|
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// --- Main ---
|
|
486
297
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
parentPort!.on("message", (msg: DaemonMessage) => {
|
|
493
|
-
handleMessage(msg).catch(e => swallow.warn("daemon:handleMessage", e));
|
|
494
|
-
});
|
|
495
|
-
});
|
|
298
|
+
await Promise.allSettled(writeOps);
|
|
299
|
+
return counts;
|
|
300
|
+
}
|
package/src/reflection.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* At session end, reviews own performance: tool failures, runaway detections,
|
|
5
5
|
* low retrieval utilization, wasted tokens. If problems exceeded thresholds,
|
|
6
|
-
* generates a structured reflection via
|
|
6
|
+
* generates a structured reflection via the configured LLM, stored as high-importance memory.
|
|
7
7
|
* Retrieved when similar situations arise in future sessions.
|
|
8
8
|
*
|
|
9
9
|
* Ported from kongbrain — takes SurrealStore/EmbeddingService as params.
|
package/src/schema.surql
CHANGED
|
@@ -97,6 +97,8 @@ DEFINE FIELD IF NOT EXISTS last_active ON session TYPE datetime DEFAULT time::no
|
|
|
97
97
|
DEFINE FIELD IF NOT EXISTS turn_count ON session TYPE int DEFAULT 0;
|
|
98
98
|
DEFINE FIELD IF NOT EXISTS total_input_tokens ON session TYPE int DEFAULT 0;
|
|
99
99
|
DEFINE FIELD IF NOT EXISTS total_output_tokens ON session TYPE int DEFAULT 0;
|
|
100
|
+
DEFINE FIELD IF NOT EXISTS ended_at ON session TYPE option<datetime>;
|
|
101
|
+
DEFINE FIELD IF NOT EXISTS cleanup_completed ON session TYPE bool DEFAULT false;
|
|
100
102
|
|
|
101
103
|
-- Long-term memory (episodic → consolidated)
|
|
102
104
|
DEFINE TABLE IF NOT EXISTS memory SCHEMALESS;
|
package/src/state.ts
CHANGED
|
@@ -34,7 +34,11 @@ export class SessionState {
|
|
|
34
34
|
// Memory daemon
|
|
35
35
|
daemon: MemoryDaemon | null = null;
|
|
36
36
|
newContentTokens = 0;
|
|
37
|
-
readonly DAEMON_TOKEN_THRESHOLD =
|
|
37
|
+
readonly DAEMON_TOKEN_THRESHOLD = 4000;
|
|
38
|
+
lastDaemonFlushTurnCount = 0;
|
|
39
|
+
|
|
40
|
+
// Cleanup tracking
|
|
41
|
+
cleanedUp = false;
|
|
38
42
|
|
|
39
43
|
// Current adaptive config (set by orchestrator preflight each turn)
|
|
40
44
|
currentConfig: AdaptiveConfig | null = null;
|
|
@@ -46,6 +50,7 @@ export class SessionState {
|
|
|
46
50
|
agentId = "";
|
|
47
51
|
projectId = "";
|
|
48
52
|
taskId = "";
|
|
53
|
+
surrealSessionId = "";
|
|
49
54
|
|
|
50
55
|
constructor(sessionId: string, sessionKey: string) {
|
|
51
56
|
this.sessionId = sessionId;
|