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 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.15
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.15",
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",
@@ -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
@@ -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
- return Array.from(result.vector);
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
  }
@@ -132,6 +132,7 @@ async function trackArtifact(
132
132
  await linkToRelevantConcepts(
133
133
  artifactId, "artifact_mentions", description,
134
134
  state.store, state.embeddings, "artifact:concepts",
135
+ 5, 0.65, emb,
135
136
  );
136
137
  }
137
138
  }
@@ -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 { upsertAndLinkConcepts, linkConceptHierarchy, linkToRelevantConcepts } from "./concept-extract.js";
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 upsertAndLinkConcepts(skillId, "skill_uses_concept", content, store, embeddings, "daemon:skill:concepts");
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
  }