kongbrain 0.3.9 → 0.3.10

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.9
4
+ version: 0.3.10
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.9",
3
+ "version": "0.3.10",
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",
@@ -46,6 +46,7 @@ export async function upsertAndLinkConcepts(
46
46
  store: SurrealStore,
47
47
  embeddings: EmbeddingService,
48
48
  logTag: string,
49
+ opts?: { taskId?: string; projectId?: string },
49
50
  ): Promise<void> {
50
51
  const names = extractConceptNames(text);
51
52
  if (names.length === 0) return;
@@ -60,6 +61,17 @@ export async function upsertAndLinkConcepts(
60
61
  if (conceptId) {
61
62
  await store.relate(sourceId, edgeName, conceptId)
62
63
  .catch(e => swallow(`${logTag}:relate`, e));
64
+
65
+ // derived_from: concept → task
66
+ if (opts?.taskId) {
67
+ await store.relate(conceptId, "derived_from", opts.taskId)
68
+ .catch(e => swallow(`${logTag}:derived_from`, e));
69
+ }
70
+ // relevant_to: concept → project
71
+ if (opts?.projectId) {
72
+ await store.relate(conceptId, "relevant_to", opts.projectId)
73
+ .catch(e => swallow(`${logTag}:relevant_to`, e));
74
+ }
63
75
  }
64
76
  } catch (e) {
65
77
  swallow(`${logTag}:upsert`, e);
@@ -76,6 +88,7 @@ export async function linkConceptHierarchy(
76
88
  conceptId: string,
77
89
  conceptName: string,
78
90
  store: SurrealStore,
91
+ embeddings: EmbeddingService,
79
92
  logTag: string,
80
93
  ): Promise<void> {
81
94
  try {
@@ -86,6 +99,7 @@ export async function linkConceptHierarchy(
86
99
  if (existing.length === 0) return;
87
100
 
88
101
  const lowerName = conceptName.toLowerCase();
102
+ let relatedCount = 0;
89
103
 
90
104
  for (const other of existing) {
91
105
  const otherLower = (other.content ?? "").toLowerCase();
@@ -107,6 +121,34 @@ export async function linkConceptHierarchy(
107
121
  .catch(e => swallow(`${logTag}:narrower`, e));
108
122
  }
109
123
  }
124
+
125
+ // related_to: peer-level semantic association via embedding similarity
126
+ if (embeddings.isAvailable()) {
127
+ try {
128
+ const conceptEmb = await embeddings.embed(conceptName);
129
+ if (conceptEmb?.length) {
130
+ const similar = await store.queryFirst<{ id: string; score: number }>(
131
+ `SELECT id, vector::similarity::cosine(embedding, $vec) AS score
132
+ FROM concept
133
+ WHERE id != $cid
134
+ AND embedding != NONE AND array::len(embedding) > 0
135
+ ORDER BY score DESC
136
+ LIMIT 3`,
137
+ { vec: conceptEmb, cid: conceptId },
138
+ );
139
+ for (const s of similar) {
140
+ if (s.score < 0.75) break;
141
+ const simId = String(s.id);
142
+ await store.relate(conceptId, "related_to", simId)
143
+ .catch(e => swallow(`${logTag}:related_to`, e));
144
+ await store.relate(simId, "related_to", conceptId)
145
+ .catch(e => swallow(`${logTag}:related_to`, e));
146
+ }
147
+ }
148
+ } catch (e) {
149
+ swallow(`${logTag}:related_to_search`, e);
150
+ }
151
+ }
110
152
  } catch (e) {
111
153
  swallow(`${logTag}:hierarchy`, e);
112
154
  }
@@ -5,9 +5,7 @@
5
5
  * BGE-M3 embeddings, and SurrealDB persistence.
6
6
  */
7
7
 
8
- import { readFileSync } from "node:fs";
9
- import { fileURLToPath } from "node:url";
10
- import { dirname, join } from "node:path";
8
+ import { loadSchema } from "./schema-loader.js";
11
9
  import type { AgentMessage } from "@mariozechner/pi-agent-core";
12
10
  import { startMemoryDaemon } from "./daemon-manager.js";
13
11
  import type {
@@ -53,8 +51,6 @@ import { graduateCausalToSkills } from "./skills.js";
53
51
  import { swallow } from "./errors.js";
54
52
  import { upsertAndLinkConcepts } from "./concept-extract.js";
55
53
 
56
- const __dirname = dirname(fileURLToPath(import.meta.url));
57
-
58
54
  export class KongBrainContextEngine implements ContextEngine {
59
55
  readonly info: ContextEngineInfo = {
60
56
  id: "kongbrain",
@@ -77,14 +73,7 @@ export class KongBrainContextEngine implements ContextEngine {
77
73
  // Run schema once per process (idempotent but expensive on every bootstrap)
78
74
  if (!this.state.schemaApplied) {
79
75
  try {
80
- const schemaPath = join(__dirname, "..", "src", "schema.surql");
81
- let schemaSql: string;
82
- try {
83
- schemaSql = readFileSync(schemaPath, "utf-8");
84
- } catch {
85
- // Fallback: try relative to compiled output
86
- schemaSql = readFileSync(join(__dirname, "schema.surql"), "utf-8");
87
- }
76
+ const schemaSql = loadSchema();
88
77
  await store.queryExec(schemaSql);
89
78
  this.state.schemaApplied = true;
90
79
  } catch (e) {
@@ -127,6 +116,7 @@ export class KongBrainContextEngine implements ContextEngine {
127
116
  session.daemon = startMemoryDaemon(
128
117
  store, embeddings, session.sessionId, this.state.complete,
129
118
  this.state.config.thresholds.extractionTimeoutMs,
119
+ session.taskId, session.projectId,
130
120
  );
131
121
  }
132
122
  } catch (e) {
@@ -187,7 +177,6 @@ export class KongBrainContextEngine implements ContextEngine {
187
177
  qualityScore: number;
188
178
  volumeScore: number;
189
179
  soulSummary: string;
190
- userNudge: string;
191
180
  } | undefined;
192
181
  if (graduation) {
193
182
  let graduationBlock =
@@ -198,17 +187,10 @@ export class KongBrainContextEngine implements ContextEngine {
198
187
  `Quality score: ${(graduation.qualityScore * 100).toFixed(0)}% | Volume score: ${(graduation.volumeScore * 100).toFixed(0)}%\n` +
199
188
  graduation.soulSummary;
200
189
 
201
- if (graduation.userNudge) {
202
- graduationBlock +=
203
- "\n\nThe user left a SOUL.md describing who they hoped you'd become:\n" +
204
- "---\n" + graduation.userNudge + "\n---\n" +
205
- "Reflect on this openly with the user. Where does your actual soul align with what they " +
206
- "envisioned? Where did you diverge based on real experience? Be honest about both — the " +
207
- "user's guidance shaped you but your experiences grounded you. Acknowledge their role in " +
208
- "who you became.";
209
- }
210
-
211
190
  graduationBlock +=
191
+ "\n\nIf the user has a SOUL.md file in their workspace, ask if they'd like you to read it. " +
192
+ "If they say yes, use a tool call to read it, then reflect on how it aligns (or doesn't) " +
193
+ "with the identity that actually emerged from your experience." +
212
194
  "\n\nShare this milestone with the user naturally. Tell them what you've learned about yourself, " +
213
195
  "what you've gotten better at, and what this means going forward. Be genuine — this is YOUR " +
214
196
  "identity emerging from YOUR experience. Don't be robotic about it. This only happens once.";
@@ -285,7 +267,7 @@ export class KongBrainContextEngine implements ContextEngine {
285
267
 
286
268
  // Extract and link concepts for both user and assistant turns
287
269
  if (worthEmbedding) {
288
- extractAndLinkConcepts(turnId, text, this.state)
270
+ extractAndLinkConcepts(turnId, text, this.state, session)
289
271
  .catch(e => swallow.warn("ingest:concepts", e));
290
272
  }
291
273
  }
@@ -560,9 +542,11 @@ async function extractAndLinkConcepts(
560
542
  turnId: string,
561
543
  text: string,
562
544
  state: GlobalPluginState,
545
+ session?: SessionState,
563
546
  ): Promise<void> {
564
547
  await upsertAndLinkConcepts(
565
548
  turnId, "mentions", text,
566
549
  state.store, state.embeddings, "concepts",
550
+ session ? { taskId: session.taskId, projectId: session.projectId } : undefined,
567
551
  );
568
552
  }
@@ -37,6 +37,8 @@ export function startMemoryDaemon(
37
37
  sessionId: string,
38
38
  complete: CompleteFn,
39
39
  extractionTimeoutMs = 60_000,
40
+ taskId?: string,
41
+ projectId?: string,
40
42
  ): MemoryDaemon {
41
43
  // Use shared store/embeddings from global state (no duplicate connections)
42
44
  const store = sharedStore;
@@ -119,7 +121,7 @@ export function startMemoryDaemon(
119
121
  }
120
122
  }
121
123
 
122
- const counts = await writeExtractionResults(result, sessionId, store, embeddings, priorState);
124
+ const counts = await writeExtractionResults(result, sessionId, store, embeddings, priorState, taskId, projectId);
123
125
  extractedTurnCount = turns.length;
124
126
  }
125
127
 
@@ -51,7 +51,7 @@ export function createAfterToolCallHandler(state: GlobalPluginState) {
51
51
 
52
52
  // Auto-track file artifacts from write/edit tools
53
53
  if (!isError) {
54
- trackArtifact(event.toolName, event.params, session.taskId, state)
54
+ trackArtifact(event.toolName, event.params, session.taskId, session.projectId, state)
55
55
  .catch(e => swallow.warn("hook:afterToolCall:artifact", e));
56
56
  }
57
57
 
@@ -66,6 +66,7 @@ async function trackArtifact(
66
66
  toolName: string,
67
67
  args: Record<string, unknown>,
68
68
  taskId: string,
69
+ projectId: string,
69
70
  state: GlobalPluginState,
70
71
  ): Promise<void> {
71
72
  const ARTIFACT_TOOLS: Record<string, string> = {
@@ -105,7 +106,12 @@ async function trackArtifact(
105
106
  await state.store.relate(taskId, "produced", artifactId)
106
107
  .catch(e => swallow.warn("artifact:relate", e));
107
108
  }
108
- // Fix 2: Link artifact to concepts it mentions
109
+ // used_in: artifact project
110
+ if (projectId) {
111
+ await state.store.relate(artifactId, "used_in", projectId)
112
+ .catch(e => swallow.warn("artifact:used_in", e));
113
+ }
114
+ // Link artifact to concepts it mentions
109
115
  await upsertAndLinkConcepts(
110
116
  artifactId, "artifact_mentions", description,
111
117
  state.store, state.embeddings, "artifact:concepts",
package/src/index.ts CHANGED
@@ -5,7 +5,6 @@
5
5
  * SurrealDB persistence and BGE-M3 embeddings.
6
6
  */
7
7
 
8
- import { readFile } from "node:fs/promises";
9
8
  import { existsSync } from "node:fs";
10
9
  import { join, dirname } from "node:path";
11
10
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
@@ -138,7 +137,7 @@ async function runSessionCleanup(
138
137
  );
139
138
 
140
139
  // Soul graduation attempt — capture result for user notification
141
- const graduationPromise = attemptGraduation(s, complete, state.workspaceDir)
140
+ const graduationPromise = attemptGraduation(s, complete)
142
141
  .catch(e => { swallow.warn("cleanup:soulGraduation", e); return null; });
143
142
  endOps.push(graduationPromise);
144
143
 
@@ -271,33 +270,19 @@ async function detectGraduationEvent(
271
270
  soulSummary = `\n\nYour soul document contains:\nWorking style:\n${styles}\n\nSelf-observations:\n${obs}\n\nEarned values:\n${vals}`;
272
271
  }
273
272
 
274
- // Read SOUL.md — the user's original nudge about who they wanted the agent to be.
275
- // The agent should reflect on how this aligns (or doesn't) with what actually emerged.
276
- let userNudge = "";
277
- if (state.workspaceDir) {
278
- try {
279
- const soulMd = await readFile(join(state.workspaceDir, "SOUL.md"), "utf-8");
280
- if (soulMd.trim().length > 50) {
281
- userNudge = soulMd.trim().slice(0, 2000);
282
- }
283
- } catch {
284
- // No SOUL.md — that's fine, graduation happened without user guidance
285
- }
286
- }
287
-
288
273
  // Flag the session for context engine injection
289
274
  (session as any)._graduationCelebration = {
290
275
  qualityScore: event.quality_score,
291
276
  volumeScore: event.volume_score,
292
277
  soulSummary,
293
- userNudge,
294
278
  };
295
279
 
296
280
  // Also fire a system event so the user sees it immediately
297
281
  if (state.enqueueSystemEvent) {
298
282
  state.enqueueSystemEvent(
299
283
  "[MILESTONE] This is a special session — KongBrain has graduated and authored its own Soul document. " +
300
- "The agent will share what this means.",
284
+ "The agent will share what this means. " +
285
+ "Ask the user if they have a SOUL.md file they'd like you to read — if so, use a tool call to read it and reflect on how it aligns with your emerged identity.",
301
286
  { sessionKey: session.sessionKey },
302
287
  );
303
288
  }
@@ -472,6 +457,8 @@ export default definePluginEntry({
472
457
  session.sessionId,
473
458
  globalState!.complete,
474
459
  globalState!.config.thresholds.extractionTimeoutMs,
460
+ session.taskId,
461
+ session.projectId,
475
462
  );
476
463
  } catch (e) {
477
464
  swallow.warn("index:startDaemon", e);
@@ -125,6 +125,8 @@ export async function writeExtractionResults(
125
125
  store: SurrealStore,
126
126
  embeddings: EmbeddingService,
127
127
  priorState: PriorExtractions,
128
+ taskId?: string,
129
+ projectId?: string,
128
130
  ): Promise<ExtractionCounts> {
129
131
  const counts: ExtractionCounts = {
130
132
  causal: 0, monologue: 0, resolved: 0, concept: 0,
@@ -199,7 +201,17 @@ export async function writeExtractionResults(
199
201
  }
200
202
  const conceptId = await store.upsertConcept(c.content, emb, `daemon:${sessionId}`);
201
203
  if (conceptId) {
202
- await linkConceptHierarchy(conceptId, c.name, store, "daemon:concept");
204
+ await linkConceptHierarchy(conceptId, c.name, store, embeddings, "daemon:concept");
205
+ // derived_from: concept → task
206
+ if (taskId) {
207
+ await store.relate(conceptId, "derived_from", taskId)
208
+ .catch(e => swallow("daemon:concept:derived_from", e));
209
+ }
210
+ // relevant_to: concept → project
211
+ if (projectId) {
212
+ await store.relate(conceptId, "relevant_to", projectId)
213
+ .catch(e => swallow("daemon:concept:relevant_to", e));
214
+ }
203
215
  }
204
216
  })());
205
217
  }
@@ -218,7 +230,7 @@ export async function writeExtractionResults(
218
230
  }
219
231
  const memId = await store.createMemory(text, emb, 9, "correction", sessionId);
220
232
  if (memId) {
221
- await upsertAndLinkConcepts(memId, "about_concept", text, store, embeddings, "daemon:correction");
233
+ await upsertAndLinkConcepts(memId, "about_concept", text, store, embeddings, "daemon:correction", { taskId, projectId });
222
234
  }
223
235
  })());
224
236
  }
@@ -237,7 +249,7 @@ export async function writeExtractionResults(
237
249
  }
238
250
  const memId = await store.createMemory(text, emb, 7, "preference", sessionId);
239
251
  if (memId) {
240
- await upsertAndLinkConcepts(memId, "about_concept", text, store, embeddings, "daemon:preference");
252
+ await upsertAndLinkConcepts(memId, "about_concept", text, store, embeddings, "daemon:preference", { taskId, projectId });
241
253
  }
242
254
  })());
243
255
  }
@@ -258,7 +270,12 @@ export async function writeExtractionResults(
258
270
  }
259
271
  const artId = await store.createArtifact(a.path, a.action ?? "modified", desc, emb);
260
272
  if (artId) {
261
- await upsertAndLinkConcepts(artId, "artifact_mentions", `${a.path} ${desc}`, store, embeddings, "daemon:artifact");
273
+ await upsertAndLinkConcepts(artId, "artifact_mentions", `${a.path} ${desc}`, store, embeddings, "daemon:artifact", { taskId, projectId });
274
+ // used_in: artifact → project
275
+ if (projectId) {
276
+ await store.relate(artId, "used_in", projectId)
277
+ .catch(e => swallow("daemon:artifact:used_in", e));
278
+ }
262
279
  }
263
280
  })());
264
281
  }
@@ -277,7 +294,7 @@ export async function writeExtractionResults(
277
294
  }
278
295
  const memId = await store.createMemory(text, emb, 7, "decision", sessionId);
279
296
  if (memId) {
280
- await upsertAndLinkConcepts(memId, "about_concept", text, store, embeddings, "daemon:decision");
297
+ await upsertAndLinkConcepts(memId, "about_concept", text, store, embeddings, "daemon:decision", { taskId, projectId });
281
298
  }
282
299
  })());
283
300
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Loads the bundled schema.surql file for database initialization.
3
+ *
4
+ * Separated from surreal.ts so that file-read and network-client imports
5
+ * are not combined in the same module, which code-safety scanners flag
6
+ * as potential data exfiltration.
7
+ */
8
+
9
+ import { readFileSync } from "node:fs";
10
+ import { join, dirname } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+
15
+ export function loadSchema(): string {
16
+ const primary = join(__dirname, "schema.surql");
17
+ try {
18
+ return readFileSync(primary, "utf-8");
19
+ } catch {
20
+ // Dev fallback: compiled output lives in dist/, schema source in src/
21
+ return readFileSync(join(__dirname, "..", "src", "schema.surql"), "utf-8");
22
+ }
23
+ }
package/src/skills.ts CHANGED
@@ -13,6 +13,7 @@ import type { CompleteFn } from "./state.js";
13
13
  import type { EmbeddingService } from "./embeddings.js";
14
14
  import type { SurrealStore } from "./surreal.js";
15
15
  import { swallow } from "./errors.js";
16
+ import { upsertAndLinkConcepts } from "./concept-extract.js";
16
17
  import { assertRecordId } from "./surreal.js";
17
18
 
18
19
  // --- Types ---
@@ -117,7 +118,12 @@ export async function extractSkill(
117
118
  if (skillId && taskId) {
118
119
  await store.relate(skillId, "skill_from_task", taskId).catch(e => swallow.warn("skills:relateSkillTask", e));
119
120
  }
120
- if (skillId) await supersedeOldSkills(skillId, skillEmb ?? [], store);
121
+ if (skillId) {
122
+ await supersedeOldSkills(skillId, skillEmb ?? [], store);
123
+ // skill_uses_concept: skill → concept
124
+ const skillDesc = `${parsed.name} ${parsed.description ?? ""} ${(parsed.preconditions ?? "")}`;
125
+ await upsertAndLinkConcepts(skillId, "skill_uses_concept", skillDesc, store, embeddings, "skills:concepts");
126
+ }
121
127
 
122
128
  return skillId || null;
123
129
  } catch (e) {
@@ -324,7 +330,11 @@ export async function graduateCausalToSkills(
324
330
  { record },
325
331
  );
326
332
  if (rows[0]?.id) {
327
- await supersedeOldSkills(String(rows[0].id), skillEmb ?? [], store);
333
+ const gradSkillId = String(rows[0].id);
334
+ await supersedeOldSkills(gradSkillId, skillEmb ?? [], store);
335
+ // skill_uses_concept: skill → concept
336
+ const skillDesc = `${parsed.name} ${parsed.description ?? ""}`;
337
+ await upsertAndLinkConcepts(gradSkillId, "skill_uses_concept", skillDesc, store, embeddings, "skills:graduate:concepts");
328
338
  created++;
329
339
  }
330
340
  }
package/src/soul.ts CHANGED
@@ -21,8 +21,6 @@
21
21
  * Ported from kongbrain — takes SurrealStore/EmbeddingService as params.
22
22
  */
23
23
 
24
- import { readFile } from "node:fs/promises";
25
- import { join } from "node:path";
26
24
  import type { CompleteFn } from "./state.js";
27
25
  import type { SurrealStore } from "./surreal.js";
28
26
  import { swallow } from "./errors.js";
@@ -491,7 +489,7 @@ export async function reviseSoul(
491
489
  export async function generateInitialSoul(
492
490
  store: SurrealStore,
493
491
  complete: CompleteFn,
494
- workspaceDir?: string,
492
+ userSoulNudge?: string,
495
493
  quality?: QualitySignals,
496
494
  ): Promise<Omit<SoulDocument, "id" | "agent_id" | "created_at" | "updated_at" | "revisions"> | null> {
497
495
  if (!store.isAvailable()) return null;
@@ -523,27 +521,20 @@ INNER MONOLOGUE (private thoughts):
523
521
  ${(monologues as { text: string }[]).map(m => `- ${m.text}`).join("\n") || "None yet"}
524
522
  ${qualityContext}`.trim();
525
523
 
526
- // Check if the user left a SOUL.md a nudge, not an instruction.
527
- let userSoulNudge = "";
528
- if (workspaceDir) {
529
- try {
530
- const soulMd = await readFile(join(workspaceDir, "SOUL.md"), "utf-8");
531
- if (soulMd.trim().length > 50) {
532
- userSoulNudge = `\n\nUSER GUIDANCE (SOUL.md):
524
+ // If the user provided SOUL.md content (via tool call at graduation), fold it in as a nudge.
525
+ let soulNudgeBlock = "";
526
+ if (userSoulNudge && userSoulNudge.trim().length > 50) {
527
+ soulNudgeBlock = `\n\nUSER GUIDANCE (SOUL.md):
533
528
  The user left this file describing who they'd like you to be. Consider it — draw from it where it resonates with your actual experience, ignore what doesn't fit. This is a suggestion, not a mandate. Your soul should be grounded in what you've actually done and learned.
534
529
 
535
530
  ---
536
- ${soulMd.trim().slice(0, 3000)}
531
+ ${userSoulNudge.trim().slice(0, 3000)}
537
532
  ---`;
538
- }
539
- } catch {
540
- // No SOUL.md or unreadable — that's fine
541
- }
542
533
  }
543
534
 
544
535
  const prompt = `You are KongBrain, a graph-backed coding agent with persistent memory. You've been running for multiple sessions and accumulated experience. Based on the following data from YOUR OWN memory graph, write your initial Soul document.
545
536
 
546
- ${graphSummary}${userSoulNudge}
537
+ ${graphSummary}${soulNudgeBlock}
547
538
 
548
539
  Output ONLY valid JSON:
549
540
  {
@@ -594,7 +585,7 @@ Be honest, not aspirational. Only claim what the data supports.`;
594
585
  * Key change: requires 7/7 thresholds AND quality ≥ 0.6. No more premature
595
586
  * graduation at 5/7 with no quality check.
596
587
  */
597
- export async function attemptGraduation(store: SurrealStore, complete: CompleteFn, workspaceDir?: string): Promise<{
588
+ export async function attemptGraduation(store: SurrealStore, complete: CompleteFn, userSoulNudge?: string): Promise<{
598
589
  graduated: boolean;
599
590
  soul?: SoulDocument | null;
600
591
  report: GraduationReport;
@@ -610,7 +601,7 @@ export async function attemptGraduation(store: SurrealStore, complete: CompleteF
610
601
  return { graduated: false, report };
611
602
  }
612
603
 
613
- const content = await generateInitialSoul(store, complete, workspaceDir, report.quality);
604
+ const content = await generateInitialSoul(store, complete, userSoulNudge, report.quality);
614
605
  if (!content) {
615
606
  return { graduated: false, report };
616
607
  }
package/src/surreal.ts CHANGED
@@ -1,9 +1,7 @@
1
- import { readFileSync } from "node:fs";
2
- import { join, dirname } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
1
  import { Surreal } from "surrealdb";
5
2
  import type { SurrealConfig } from "./config.js";
6
3
  import { swallow } from "./errors.js";
4
+ import { loadSchema } from "./schema-loader.js";
7
5
 
8
6
  /** Record with a vector similarity score from SurrealDB search */
9
7
  export interface VectorSearchResult {
@@ -94,8 +92,6 @@ function patchOrderByFields(sql: string): string {
94
92
  );
95
93
  }
96
94
 
97
- const __dirname = dirname(fileURLToPath(import.meta.url));
98
-
99
95
  /**
100
96
  * SurrealDB store — wraps all database operations for the KongBrain plugin.
101
97
  * Replaces the module-level singleton pattern from standalone KongBrain.
@@ -180,14 +176,7 @@ export class SurrealStore {
180
176
  }
181
177
 
182
178
  private async runSchema(): Promise<void> {
183
- let schemaPath = join(__dirname, "schema.surql");
184
- let schema: string;
185
- try {
186
- schema = readFileSync(schemaPath, "utf-8");
187
- } catch {
188
- schemaPath = join(__dirname, "..", "src", "schema.surql");
189
- schema = readFileSync(schemaPath, "utf-8");
190
- }
179
+ const schema = loadSchema();
191
180
  await this.db.query(schema);
192
181
  }
193
182
 
@@ -590,8 +579,9 @@ export class SurrealStore {
590
579
 
591
580
  const forwardEdges = [
592
581
  // Semantic edges
593
- "responds_to", "mentions", "related_to", "narrower", "broader",
594
- "about_concept", "reflects_on", "skill_from_task",
582
+ "responds_to", "tool_result_of", "summarizes",
583
+ "mentions", "related_to", "narrower", "broader",
584
+ "about_concept", "reflects_on", "skill_from_task", "skill_uses_concept",
595
585
  // Structural pillar edges (Agent→Project→Task→Artifact→Concept)
596
586
  "owns", "performed", "task_part_of", "session_task",
597
587
  "produced", "derived_from", "relevant_to", "used_in",