kongbrain 0.3.15 → 0.3.16
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/SKILL.md +1 -1
- package/package.json +1 -1
- package/src/concept-extract.ts +2 -1
- package/src/deferred-cleanup.ts +4 -4
- package/src/embeddings.ts +18 -1
- package/src/hooks/after-tool-call.ts +1 -0
- package/src/memory-daemon.ts +6 -6
- package/src/skills.ts +2 -2
package/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: kongbrain
|
|
3
3
|
description: Graph-backed persistent memory engine for OpenClaw. Replaces the default context window with SurrealDB + vector embeddings that learn across sessions.
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.16
|
|
5
5
|
homepage: https://github.com/42U/kongbrain
|
|
6
6
|
metadata:
|
|
7
7
|
openclaw:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kongbrain",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.16",
|
|
4
4
|
"description": "Graph-backed persistent memory engine for OpenClaw. Replaces the default context window with SurrealDB + vector embeddings that learn across sessions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/concept-extract.ts
CHANGED
|
@@ -98,10 +98,11 @@ export async function linkToRelevantConcepts(
|
|
|
98
98
|
logTag: string,
|
|
99
99
|
limit = 5,
|
|
100
100
|
threshold = 0.65,
|
|
101
|
+
precomputedVec?: number[] | null,
|
|
101
102
|
): Promise<void> {
|
|
102
103
|
if (!embeddings.isAvailable() || !text) return;
|
|
103
104
|
try {
|
|
104
|
-
const vec = await embeddings.embed(text);
|
|
105
|
+
const vec = precomputedVec?.length ? precomputedVec : await embeddings.embed(text);
|
|
105
106
|
if (!vec?.length) return;
|
|
106
107
|
const matches = await store.queryFirst<{ id: string; score: number }>(
|
|
107
108
|
`SELECT id, vector::similarity::cosine(embedding, $vec) AS score
|
package/src/deferred-cleanup.ts
CHANGED
|
@@ -87,8 +87,8 @@ async function processOrphanedSession(
|
|
|
87
87
|
complete: CompleteFn,
|
|
88
88
|
): Promise<void> {
|
|
89
89
|
// Load turns for extraction via part_of edges (turn->part_of->session)
|
|
90
|
-
const turns = await store.queryFirst<{ role: string; text: string; tool_name?: string }>(
|
|
91
|
-
`SELECT role, text, tool_name, created_at FROM turn
|
|
90
|
+
const turns = await store.queryFirst<{ id: string; role: string; text: string; tool_name?: string }>(
|
|
91
|
+
`SELECT id, role, text, tool_name, created_at FROM turn
|
|
92
92
|
WHERE id IN (SELECT VALUE in FROM part_of WHERE out = $sid)
|
|
93
93
|
ORDER BY created_at ASC LIMIT 50`,
|
|
94
94
|
{ sid: surrealSessionId },
|
|
@@ -100,7 +100,7 @@ async function processOrphanedSession(
|
|
|
100
100
|
|
|
101
101
|
// Run daemon extraction
|
|
102
102
|
const priorState: PriorExtractions = { conceptNames: [], artifactPaths: [], skillNames: [] };
|
|
103
|
-
const turnData = turns.map(t => ({ role: t.role, text: t.text, tool_name: t.tool_name }));
|
|
103
|
+
const turnData = turns.map(t => ({ turnId: String(t.id), role: t.role, text: t.text, tool_name: t.tool_name }));
|
|
104
104
|
const transcript = buildTranscript(turnData);
|
|
105
105
|
const systemPrompt = buildSystemPrompt(false, false, priorState);
|
|
106
106
|
|
|
@@ -138,7 +138,7 @@ async function processOrphanedSession(
|
|
|
138
138
|
const keys = Object.keys(result);
|
|
139
139
|
console.warn(`[deferred] parsed ${keys.length} keys: ${keys.join(", ")}`);
|
|
140
140
|
if (keys.length > 0) {
|
|
141
|
-
await writeExtractionResults(result, surrealSessionId, store, embeddings, priorState);
|
|
141
|
+
await writeExtractionResults(result, surrealSessionId, store, embeddings, priorState, undefined, undefined, turnData);
|
|
142
142
|
console.warn(`[deferred] wrote extraction results for ${surrealSessionId}`);
|
|
143
143
|
}
|
|
144
144
|
} else {
|
package/src/embeddings.ts
CHANGED
|
@@ -11,6 +11,9 @@ export class EmbeddingService {
|
|
|
11
11
|
private model: LlamaModel | null = null;
|
|
12
12
|
private ctx: LlamaEmbeddingContext | null = null;
|
|
13
13
|
private ready = false;
|
|
14
|
+
/** LRU embedding cache keyed by text, capped at maxCacheSize entries. */
|
|
15
|
+
private cache = new Map<string, number[]>();
|
|
16
|
+
private readonly maxCacheSize = 512;
|
|
14
17
|
|
|
15
18
|
constructor(private readonly config: EmbeddingConfig) {}
|
|
16
19
|
|
|
@@ -39,8 +42,21 @@ export class EmbeddingService {
|
|
|
39
42
|
|
|
40
43
|
async embed(text: string): Promise<number[]> {
|
|
41
44
|
if (!this.ready || !this.ctx) throw new Error("Embeddings not initialized");
|
|
45
|
+
const cached = this.cache.get(text);
|
|
46
|
+
if (cached) {
|
|
47
|
+
// Move to end for LRU freshness
|
|
48
|
+
this.cache.delete(text);
|
|
49
|
+
this.cache.set(text, cached);
|
|
50
|
+
return cached;
|
|
51
|
+
}
|
|
42
52
|
const result = await this.ctx.getEmbeddingFor(text);
|
|
43
|
-
|
|
53
|
+
const vec = Array.from(result.vector);
|
|
54
|
+
// Evict oldest if at capacity
|
|
55
|
+
if (this.cache.size >= this.maxCacheSize) {
|
|
56
|
+
this.cache.delete(this.cache.keys().next().value!);
|
|
57
|
+
}
|
|
58
|
+
this.cache.set(text, vec);
|
|
59
|
+
return vec;
|
|
44
60
|
}
|
|
45
61
|
|
|
46
62
|
async embedBatch(texts: string[]): Promise<number[][]> {
|
|
@@ -61,6 +77,7 @@ export class EmbeddingService {
|
|
|
61
77
|
await this.ctx?.dispose();
|
|
62
78
|
await this.model?.dispose();
|
|
63
79
|
this.ready = false;
|
|
80
|
+
this.cache.clear();
|
|
64
81
|
} catch (e) {
|
|
65
82
|
swallow("embeddings:dispose", e);
|
|
66
83
|
}
|
package/src/memory-daemon.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { SurrealStore } from "./surreal.js";
|
|
|
13
13
|
import type { EmbeddingService } from "./embeddings.js";
|
|
14
14
|
import { swallow } from "./errors.js";
|
|
15
15
|
import { assertRecordId } from "./surreal.js";
|
|
16
|
-
import {
|
|
16
|
+
import { linkConceptHierarchy, linkToRelevantConcepts } from "./concept-extract.js";
|
|
17
17
|
|
|
18
18
|
// --- Build the extraction prompt ---
|
|
19
19
|
|
|
@@ -254,7 +254,7 @@ export async function writeExtractionResults(
|
|
|
254
254
|
}
|
|
255
255
|
const memId = await store.createMemory(text, emb, 9, "correction", sessionId);
|
|
256
256
|
if (memId) {
|
|
257
|
-
await linkToRelevantConcepts(memId, "about_concept", text, store, embeddings, "daemon:correction:about_concept");
|
|
257
|
+
await linkToRelevantConcepts(memId, "about_concept", text, store, embeddings, "daemon:correction:about_concept", 5, 0.65, emb);
|
|
258
258
|
}
|
|
259
259
|
})());
|
|
260
260
|
}
|
|
@@ -273,7 +273,7 @@ export async function writeExtractionResults(
|
|
|
273
273
|
}
|
|
274
274
|
const memId = await store.createMemory(text, emb, 7, "preference", sessionId);
|
|
275
275
|
if (memId) {
|
|
276
|
-
await linkToRelevantConcepts(memId, "about_concept", text, store, embeddings, "daemon:preference:about_concept");
|
|
276
|
+
await linkToRelevantConcepts(memId, "about_concept", text, store, embeddings, "daemon:preference:about_concept", 5, 0.65, emb);
|
|
277
277
|
}
|
|
278
278
|
})());
|
|
279
279
|
}
|
|
@@ -294,7 +294,7 @@ export async function writeExtractionResults(
|
|
|
294
294
|
}
|
|
295
295
|
const artId = await store.createArtifact(a.path, a.action ?? "modified", desc, emb);
|
|
296
296
|
if (artId) {
|
|
297
|
-
await linkToRelevantConcepts(artId, "artifact_mentions", `${a.path} ${desc}`, store, embeddings, "daemon:artifact:artifact_mentions");
|
|
297
|
+
await linkToRelevantConcepts(artId, "artifact_mentions", `${a.path} ${desc}`, store, embeddings, "daemon:artifact:artifact_mentions", 5, 0.65, emb);
|
|
298
298
|
// used_in: artifact → project
|
|
299
299
|
if (projectId) {
|
|
300
300
|
await store.relate(artId, "used_in", projectId)
|
|
@@ -318,7 +318,7 @@ export async function writeExtractionResults(
|
|
|
318
318
|
}
|
|
319
319
|
const memId = await store.createMemory(text, emb, 7, "decision", sessionId);
|
|
320
320
|
if (memId) {
|
|
321
|
-
await linkToRelevantConcepts(memId, "about_concept", text, store, embeddings, "daemon:decision:about_concept");
|
|
321
|
+
await linkToRelevantConcepts(memId, "about_concept", text, store, embeddings, "daemon:decision:about_concept", 5, 0.65, emb);
|
|
322
322
|
}
|
|
323
323
|
})());
|
|
324
324
|
}
|
|
@@ -361,7 +361,7 @@ export async function writeExtractionResults(
|
|
|
361
361
|
.catch(e => swallow.warn("daemon:skill:skill_from_task", e));
|
|
362
362
|
}
|
|
363
363
|
// skill_uses_concept: skill → concept
|
|
364
|
-
await
|
|
364
|
+
await linkToRelevantConcepts(skillId, "skill_uses_concept", content, store, embeddings, "daemon:skill:concepts", 5, 0.65, emb);
|
|
365
365
|
}
|
|
366
366
|
} catch (e) {
|
|
367
367
|
swallow.warn("daemon:createSkill", e);
|
package/src/skills.ts
CHANGED
|
@@ -122,7 +122,7 @@ export async function extractSkill(
|
|
|
122
122
|
await supersedeOldSkills(skillId, skillEmb ?? [], store);
|
|
123
123
|
// skill_uses_concept: skill → concept
|
|
124
124
|
const skillDesc = `${parsed.name} ${parsed.description ?? ""} ${(parsed.preconditions ?? "")}`;
|
|
125
|
-
await linkToRelevantConcepts(skillId, "skill_uses_concept", skillDesc, store, embeddings, "skills:concepts");
|
|
125
|
+
await linkToRelevantConcepts(skillId, "skill_uses_concept", skillDesc, store, embeddings, "skills:concepts", 5, 0.65, skillEmb);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
return skillId || null;
|
|
@@ -334,7 +334,7 @@ export async function graduateCausalToSkills(
|
|
|
334
334
|
await supersedeOldSkills(gradSkillId, skillEmb ?? [], store);
|
|
335
335
|
// skill_uses_concept: skill → concept
|
|
336
336
|
const skillDesc = `${parsed.name} ${parsed.description ?? ""}`;
|
|
337
|
-
await linkToRelevantConcepts(gradSkillId, "skill_uses_concept", skillDesc, store, embeddings, "skills:graduate:concepts");
|
|
337
|
+
await linkToRelevantConcepts(gradSkillId, "skill_uses_concept", skillDesc, store, embeddings, "skills:graduate:concepts", 5, 0.65, skillEmb);
|
|
338
338
|
created++;
|
|
339
339
|
}
|
|
340
340
|
}
|