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 +1 -1
- package/package.json +1 -1
- package/src/concept-extract.ts +42 -0
- package/src/context-engine.ts +9 -25
- package/src/daemon-manager.ts +3 -1
- package/src/hooks/after-tool-call.ts +8 -2
- package/src/index.ts +5 -18
- package/src/memory-daemon.ts +22 -5
- package/src/schema-loader.ts +23 -0
- package/src/skills.ts +12 -2
- package/src/soul.ts +9 -18
- package/src/surreal.ts +5 -15
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.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.
|
|
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",
|
package/src/concept-extract.ts
CHANGED
|
@@ -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
|
}
|
package/src/context-engine.ts
CHANGED
|
@@ -5,9 +5,7 @@
|
|
|
5
5
|
* BGE-M3 embeddings, and SurrealDB persistence.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
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
|
|
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
|
}
|
package/src/daemon-manager.ts
CHANGED
|
@@ -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
|
-
//
|
|
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
|
|
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);
|
package/src/memory-daemon.ts
CHANGED
|
@@ -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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
527
|
-
let
|
|
528
|
-
if (
|
|
529
|
-
|
|
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
|
-
${
|
|
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}${
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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", "
|
|
594
|
-
"
|
|
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",
|