kongbrain 0.1.0
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/LICENSE +21 -0
- package/README.md +385 -0
- package/openclaw.plugin.json +66 -0
- package/package.json +65 -0
- package/src/acan.ts +309 -0
- package/src/causal.ts +237 -0
- package/src/cognitive-check.ts +330 -0
- package/src/config.ts +64 -0
- package/src/context-engine.ts +487 -0
- package/src/daemon-manager.ts +148 -0
- package/src/daemon-types.ts +65 -0
- package/src/embeddings.ts +77 -0
- package/src/errors.ts +43 -0
- package/src/graph-context.ts +989 -0
- package/src/hooks/after-tool-call.ts +99 -0
- package/src/hooks/before-prompt-build.ts +44 -0
- package/src/hooks/before-tool-call.ts +86 -0
- package/src/hooks/llm-output.ts +173 -0
- package/src/identity.ts +218 -0
- package/src/index.ts +435 -0
- package/src/intent.ts +190 -0
- package/src/memory-daemon.ts +495 -0
- package/src/orchestrator.ts +348 -0
- package/src/prefetch.ts +200 -0
- package/src/reflection.ts +280 -0
- package/src/retrieval-quality.ts +266 -0
- package/src/schema.surql +387 -0
- package/src/skills.ts +343 -0
- package/src/soul.ts +936 -0
- package/src/state.ts +119 -0
- package/src/surreal.ts +1371 -0
- package/src/tools/core-memory.ts +120 -0
- package/src/tools/introspect.ts +329 -0
- package/src/tools/recall.ts +102 -0
- package/src/wakeup.ts +318 -0
- package/src/workspace-migrate.ts +752 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cognitive Check — Periodic reasoning over retrieved context.
|
|
3
|
+
*
|
|
4
|
+
* Fires every few turns to evaluate what was retrieved, produce behavioral
|
|
5
|
+
* directives for the next turn, and grade retrieval quality with LLM-judged
|
|
6
|
+
* relevance scores that feed back into ACAN training.
|
|
7
|
+
*
|
|
8
|
+
* Ported from kongbrain — per-session state via WeakMap, takes SurrealStore param.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { CompleteFn, SessionState } from "./state.js";
|
|
12
|
+
import type { SurrealStore } from "./surreal.js";
|
|
13
|
+
import { swallow } from "./errors.js";
|
|
14
|
+
|
|
15
|
+
// --- Types ---
|
|
16
|
+
|
|
17
|
+
export interface CognitiveDirective {
|
|
18
|
+
type: "repeat" | "continuation" | "contradiction" | "noise" | "insight";
|
|
19
|
+
target: string;
|
|
20
|
+
instruction: string;
|
|
21
|
+
priority: "high" | "medium" | "low";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface RetrievalGrade {
|
|
25
|
+
id: string;
|
|
26
|
+
relevant: boolean;
|
|
27
|
+
reason: string;
|
|
28
|
+
score: number;
|
|
29
|
+
learned: boolean;
|
|
30
|
+
resolved: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface UserPreference {
|
|
34
|
+
observation: string;
|
|
35
|
+
confidence: "high" | "medium";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface CognitiveCheckResult {
|
|
39
|
+
directives: CognitiveDirective[];
|
|
40
|
+
grades: RetrievalGrade[];
|
|
41
|
+
sessionContinuity: "continuation" | "repeat" | "new_topic" | "tangent";
|
|
42
|
+
preferences: UserPreference[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface CognitiveCheckInput {
|
|
46
|
+
sessionId: string;
|
|
47
|
+
userQuery: string;
|
|
48
|
+
responseText: string;
|
|
49
|
+
retrievedNodes: { id: string; text: string; score: number; table: string }[];
|
|
50
|
+
recentTurns: { role: string; text: string }[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- Per-session state ---
|
|
54
|
+
|
|
55
|
+
interface CognitiveState {
|
|
56
|
+
pendingDirectives: CognitiveDirective[];
|
|
57
|
+
sessionContinuity: string;
|
|
58
|
+
checkInFlight: boolean;
|
|
59
|
+
suppressedNodeIds: Set<string>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const sessionState = new WeakMap<SessionState, CognitiveState>();
|
|
63
|
+
|
|
64
|
+
function getState(session: SessionState): CognitiveState {
|
|
65
|
+
let state = sessionState.get(session);
|
|
66
|
+
if (!state) {
|
|
67
|
+
state = {
|
|
68
|
+
pendingDirectives: [],
|
|
69
|
+
sessionContinuity: "new_topic",
|
|
70
|
+
checkInFlight: false,
|
|
71
|
+
suppressedNodeIds: new Set(),
|
|
72
|
+
};
|
|
73
|
+
sessionState.set(session, state);
|
|
74
|
+
}
|
|
75
|
+
return state;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// --- Constants ---
|
|
79
|
+
|
|
80
|
+
const DIRECTIVE_TYPES = new Set(["repeat", "continuation", "contradiction", "noise", "insight"]);
|
|
81
|
+
const PRIORITIES = new Set(["high", "medium", "low"]);
|
|
82
|
+
const CONTINUITY_TYPES = new Set(["continuation", "repeat", "new_topic", "tangent"]);
|
|
83
|
+
const VALID_RECORD_ID = /^[a-z_]+:[a-zA-Z0-9_]+$/;
|
|
84
|
+
|
|
85
|
+
// --- Public API ---
|
|
86
|
+
|
|
87
|
+
/** Returns true on turn 2, then every 3 turns (2, 5, 8, 11...). False if in-flight. */
|
|
88
|
+
export function shouldRunCheck(turnCount: number, session: SessionState): boolean {
|
|
89
|
+
const state = getState(session);
|
|
90
|
+
if (state.checkInFlight) return false;
|
|
91
|
+
if (turnCount < 2) return false;
|
|
92
|
+
return turnCount === 2 || (turnCount - 2) % 3 === 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function getPendingDirectives(session: SessionState): CognitiveDirective[] {
|
|
96
|
+
return getState(session).pendingDirectives;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function clearPendingDirectives(session: SessionState): void {
|
|
100
|
+
getState(session).pendingDirectives = [];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function getSessionContinuity(session: SessionState): string {
|
|
104
|
+
return getState(session).sessionContinuity;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getSuppressedNodeIds(session: SessionState): ReadonlySet<string> {
|
|
108
|
+
return getState(session).suppressedNodeIds;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Fire-and-forget LLM call. Stores directives, writes grades to DB. */
|
|
112
|
+
export async function runCognitiveCheck(
|
|
113
|
+
params: CognitiveCheckInput,
|
|
114
|
+
session: SessionState,
|
|
115
|
+
store: SurrealStore,
|
|
116
|
+
complete: CompleteFn,
|
|
117
|
+
): Promise<void> {
|
|
118
|
+
const state = getState(session);
|
|
119
|
+
if (state.checkInFlight) return;
|
|
120
|
+
if (params.retrievedNodes.length === 0) return;
|
|
121
|
+
|
|
122
|
+
state.checkInFlight = true;
|
|
123
|
+
try {
|
|
124
|
+
// Build input sections
|
|
125
|
+
const sections: string[] = [];
|
|
126
|
+
sections.push(`[QUERY] ${params.userQuery.slice(0, 500)}`);
|
|
127
|
+
sections.push(`[RESPONSE] ${params.responseText.slice(0, 500)}`);
|
|
128
|
+
|
|
129
|
+
const nodeLines = params.retrievedNodes
|
|
130
|
+
.slice(0, 20)
|
|
131
|
+
.map(n => `- ${n.id} (score: ${n.score.toFixed(2)}): ${n.text.slice(0, 150)}`);
|
|
132
|
+
sections.push(`[RETRIEVED]\n${nodeLines.join("\n")}`);
|
|
133
|
+
|
|
134
|
+
if (params.recentTurns.length > 0) {
|
|
135
|
+
const trajectory = params.recentTurns
|
|
136
|
+
.slice(-6)
|
|
137
|
+
.map(t => `[${t.role}] ${(t.text ?? "").slice(0, 200)}`)
|
|
138
|
+
.join("\n");
|
|
139
|
+
sections.push(`[TRAJECTORY]\n${trajectory}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const response = await complete({
|
|
143
|
+
system: `Assess the retrieved context served to an AI assistant. Return JSON:
|
|
144
|
+
|
|
145
|
+
"directives": [{type, target, instruction, priority}] — max 3. Types:
|
|
146
|
+
"repeat": same topic discussed in a prior session — instruct to acknowledge and build on it
|
|
147
|
+
"continuation": user is continuing prior work — instruct to maintain thread
|
|
148
|
+
"contradiction": retrieved info conflicts with current conversation — flag it
|
|
149
|
+
"noise": node is irrelevant despite high similarity score — instruct to ignore
|
|
150
|
+
"insight": useful pattern the model should lean into
|
|
151
|
+
Priority: "high" (must address), "medium" (should note), "low" (nice to know)
|
|
152
|
+
|
|
153
|
+
"grades": [{id, relevant, reason, score, learned, resolved}] — one per retrieved node. Score 0.0-1.0. "learned": true ONLY if the node is a [CORRECTION] memory AND the assistant's response already follows the correction without being prompted. "resolved": true if this memory's topic has been fully addressed/completed in the current conversation. Both default false.
|
|
154
|
+
|
|
155
|
+
"sessionContinuity": "repeat" | "continuation" | "new_topic" | "tangent"
|
|
156
|
+
|
|
157
|
+
"preferences": [{observation, confidence: "high"|"medium"}] — max 2. User communication style, values, or working preferences inferred from the conversation. Only include if clearly observable. Empty [] if nothing notable.
|
|
158
|
+
|
|
159
|
+
Return ONLY valid JSON.`,
|
|
160
|
+
messages: [{
|
|
161
|
+
role: "user",
|
|
162
|
+
content: sections.join("\n\n"),
|
|
163
|
+
}],
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const responseText = response.text;
|
|
167
|
+
|
|
168
|
+
const result = parseCheckResponse(responseText);
|
|
169
|
+
if (!result) return;
|
|
170
|
+
|
|
171
|
+
// Store directives for next turn's context formatting
|
|
172
|
+
state.pendingDirectives = result.directives;
|
|
173
|
+
state.sessionContinuity = result.sessionContinuity;
|
|
174
|
+
|
|
175
|
+
// Write grades to DB
|
|
176
|
+
if (result.grades.length > 0) {
|
|
177
|
+
await applyRetrievalGrades(result.grades, params.sessionId, store);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Correction importance adjustment based on behavioral compliance
|
|
181
|
+
const correctionGrades = result.grades.filter(g => g.id.startsWith("memory:") && g.relevant);
|
|
182
|
+
for (const g of correctionGrades) {
|
|
183
|
+
if (g.learned) {
|
|
184
|
+
// Agent followed the correction unprompted — decay toward background (floor 3)
|
|
185
|
+
await store.queryExec(
|
|
186
|
+
`UPDATE ${g.id} SET importance = math::max([3, importance - 2])`,
|
|
187
|
+
).catch(e => swallow.warn("cognitive-check:correctionDecay", e));
|
|
188
|
+
} else {
|
|
189
|
+
// Correction was relevant but agent ignored it — reinforce (cap 9)
|
|
190
|
+
await store.queryExec(
|
|
191
|
+
`UPDATE ${g.id} SET importance = math::min([9, importance + 1])`,
|
|
192
|
+
).catch(e => swallow.warn("cognitive-check:correctionReinforce", e));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Store high-confidence user preferences as session-pinned core memory
|
|
197
|
+
const highConfPrefs = result.preferences.filter(p => p.confidence === "high");
|
|
198
|
+
for (const pref of highConfPrefs) {
|
|
199
|
+
await store.createCoreMemory(
|
|
200
|
+
`[USER PREFERENCE] ${pref.observation}`,
|
|
201
|
+
"preference", 7, 1, params.sessionId,
|
|
202
|
+
).catch(e => swallow.warn("cognitive-check:preference", e));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Noise suppression — prevent re-retrieval of irrelevant nodes this session
|
|
206
|
+
for (const g of result.grades) {
|
|
207
|
+
if (!g.relevant && g.score < 0.3) {
|
|
208
|
+
state.suppressedNodeIds.add(g.id);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
for (const d of result.directives) {
|
|
212
|
+
if (d.type === "noise" && VALID_RECORD_ID.test(d.target)) {
|
|
213
|
+
state.suppressedNodeIds.add(d.target);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Mid-session resolution — mark addressed memories immediately
|
|
218
|
+
const resolvedGrades = result.grades.filter(g => g.resolved && g.id.startsWith("memory:"));
|
|
219
|
+
for (const g of resolvedGrades) {
|
|
220
|
+
await store.queryExec(
|
|
221
|
+
`UPDATE ${g.id} SET status = 'resolved', resolved_at = time::now(), resolved_by = $sid`,
|
|
222
|
+
{ sid: params.sessionId },
|
|
223
|
+
).catch(e => swallow.warn("cognitive-check:resolve", e));
|
|
224
|
+
}
|
|
225
|
+
} catch (e) {
|
|
226
|
+
swallow.warn("cognitive-check:run", e);
|
|
227
|
+
} finally {
|
|
228
|
+
state.checkInFlight = false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// --- Response parsing ---
|
|
233
|
+
|
|
234
|
+
export function parseCheckResponse(text: string): CognitiveCheckResult | null {
|
|
235
|
+
// Strip markdown fences if present
|
|
236
|
+
const stripped = text.replace(/```(?:json)?\s*/g, "").replace(/```\s*$/g, "");
|
|
237
|
+
const jsonMatch = stripped.match(/\{[\s\S]*\}/);
|
|
238
|
+
if (!jsonMatch) return null;
|
|
239
|
+
|
|
240
|
+
let raw: any;
|
|
241
|
+
try {
|
|
242
|
+
raw = JSON.parse(jsonMatch[0]);
|
|
243
|
+
} catch {
|
|
244
|
+
try {
|
|
245
|
+
raw = JSON.parse(jsonMatch[0].replace(/,\s*([}\]])/g, "$1"));
|
|
246
|
+
} catch { return null; }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Validate directives
|
|
250
|
+
const directives: CognitiveDirective[] = [];
|
|
251
|
+
if (Array.isArray(raw.directives)) {
|
|
252
|
+
for (const d of raw.directives.slice(0, 3)) {
|
|
253
|
+
if (!d.type || !d.target || !d.instruction) continue;
|
|
254
|
+
if (!DIRECTIVE_TYPES.has(d.type)) continue;
|
|
255
|
+
directives.push({
|
|
256
|
+
type: d.type,
|
|
257
|
+
target: String(d.target).slice(0, 100),
|
|
258
|
+
instruction: String(d.instruction).slice(0, 200),
|
|
259
|
+
priority: PRIORITIES.has(d.priority) ? d.priority : "medium",
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Validate grades
|
|
265
|
+
const grades: RetrievalGrade[] = [];
|
|
266
|
+
if (Array.isArray(raw.grades)) {
|
|
267
|
+
for (const g of raw.grades.slice(0, 30)) {
|
|
268
|
+
if (!g.id || typeof g.relevant !== "boolean") continue;
|
|
269
|
+
if (!VALID_RECORD_ID.test(g.id)) continue;
|
|
270
|
+
grades.push({
|
|
271
|
+
id: String(g.id),
|
|
272
|
+
relevant: Boolean(g.relevant),
|
|
273
|
+
reason: String(g.reason ?? "").slice(0, 150),
|
|
274
|
+
score: Math.max(0, Math.min(1, Number(g.score) || 0)),
|
|
275
|
+
learned: g.learned === true,
|
|
276
|
+
resolved: g.resolved === true,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Validate preferences
|
|
282
|
+
const preferences: UserPreference[] = [];
|
|
283
|
+
if (Array.isArray(raw.preferences)) {
|
|
284
|
+
for (const p of raw.preferences.slice(0, 2)) {
|
|
285
|
+
if (!p.observation) continue;
|
|
286
|
+
if (p.confidence !== "high" && p.confidence !== "medium") continue;
|
|
287
|
+
preferences.push({
|
|
288
|
+
observation: String(p.observation).slice(0, 200),
|
|
289
|
+
confidence: p.confidence,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const sessionContinuity = CONTINUITY_TYPES.has(raw.sessionContinuity)
|
|
295
|
+
? raw.sessionContinuity
|
|
296
|
+
: "new_topic";
|
|
297
|
+
|
|
298
|
+
return { directives, grades, sessionContinuity, preferences };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// --- Grade application ---
|
|
302
|
+
|
|
303
|
+
async function applyRetrievalGrades(
|
|
304
|
+
grades: RetrievalGrade[],
|
|
305
|
+
sessionId: string,
|
|
306
|
+
store: SurrealStore,
|
|
307
|
+
): Promise<void> {
|
|
308
|
+
for (const grade of grades) {
|
|
309
|
+
try {
|
|
310
|
+
// Find the most recent retrieval outcome for this memory+session
|
|
311
|
+
const row = await store.queryFirst<{ id: string }>(
|
|
312
|
+
`SELECT id, created_at FROM retrieval_outcome
|
|
313
|
+
WHERE memory_id = $id AND session_id = $sid
|
|
314
|
+
ORDER BY created_at DESC LIMIT 1`,
|
|
315
|
+
{ id: grade.id, sid: sessionId },
|
|
316
|
+
);
|
|
317
|
+
if (row?.[0]?.id) {
|
|
318
|
+
await store.queryExec(
|
|
319
|
+
`UPDATE ${row[0].id} SET llm_relevance = $score, llm_relevant = $relevant, llm_reason = $reason`,
|
|
320
|
+
{ score: grade.score, relevant: grade.relevant, reason: grade.reason },
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
// Feed relevance score into the utility cache — drives WMR provenUtility scoring
|
|
324
|
+
await store.updateUtilityCache(grade.id, grade.score).catch(e =>
|
|
325
|
+
swallow.warn("cognitive-check:utilityCache", e));
|
|
326
|
+
} catch (e) {
|
|
327
|
+
swallow.warn("cognitive-check:applyGrade", e);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface SurrealConfig {
|
|
5
|
+
url: string;
|
|
6
|
+
httpUrl: string;
|
|
7
|
+
user: string;
|
|
8
|
+
pass: string;
|
|
9
|
+
ns: string;
|
|
10
|
+
db: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface EmbeddingConfig {
|
|
14
|
+
modelPath: string;
|
|
15
|
+
dimensions: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface KongBrainConfig {
|
|
19
|
+
surreal: SurrealConfig;
|
|
20
|
+
embedding: EmbeddingConfig;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Parse plugin config from openclaw.plugin.json configSchema values,
|
|
25
|
+
* with env var overrides and sensible defaults.
|
|
26
|
+
*/
|
|
27
|
+
export function parsePluginConfig(raw?: Record<string, unknown>): KongBrainConfig {
|
|
28
|
+
const surreal = (raw?.surreal ?? {}) as Record<string, unknown>;
|
|
29
|
+
const embedding = (raw?.embedding ?? {}) as Record<string, unknown>;
|
|
30
|
+
|
|
31
|
+
// Priority: plugin config > env vars > defaults
|
|
32
|
+
const url =
|
|
33
|
+
(typeof surreal.url === "string" ? surreal.url : null) ??
|
|
34
|
+
process.env.SURREAL_URL ??
|
|
35
|
+
"ws://localhost:8042/rpc";
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
surreal: {
|
|
39
|
+
url,
|
|
40
|
+
get httpUrl() {
|
|
41
|
+
const override = (typeof surreal.httpUrl === "string" ? surreal.httpUrl : null) ??
|
|
42
|
+
process.env.SURREAL_HTTP_URL;
|
|
43
|
+
if (override) return override;
|
|
44
|
+
return this.url
|
|
45
|
+
.replace("ws://", "http://")
|
|
46
|
+
.replace("wss://", "https://")
|
|
47
|
+
.replace("/rpc", "/sql");
|
|
48
|
+
},
|
|
49
|
+
user: (typeof surreal.user === "string" ? surreal.user : null) ?? process.env.SURREAL_USER ?? "root",
|
|
50
|
+
pass: (typeof surreal.pass === "string" ? surreal.pass : null) ?? process.env.SURREAL_PASS ?? "root",
|
|
51
|
+
ns: (typeof surreal.ns === "string" ? surreal.ns : null) ?? process.env.SURREAL_NS ?? "kong",
|
|
52
|
+
db: (typeof surreal.db === "string" ? surreal.db : null) ?? process.env.SURREAL_DB ?? "memory",
|
|
53
|
+
},
|
|
54
|
+
embedding: {
|
|
55
|
+
modelPath:
|
|
56
|
+
process.env.EMBED_MODEL_PATH ??
|
|
57
|
+
(typeof embedding.modelPath === "string"
|
|
58
|
+
? embedding.modelPath
|
|
59
|
+
: join(homedir(), ".node-llama-cpp", "models", "bge-m3-q4_k_m.gguf")),
|
|
60
|
+
dimensions:
|
|
61
|
+
typeof embedding.dimensions === "number" ? embedding.dimensions : 1024,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|