kongbrain 0.3.5 → 0.3.9

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.
@@ -11,6 +11,7 @@
11
11
  import type { CompleteFn, SessionState } from "./state.js";
12
12
  import type { SurrealStore } from "./surreal.js";
13
13
  import { swallow } from "./errors.js";
14
+ import { assertRecordId } from "./surreal.js";
14
15
 
15
16
  // --- Types ---
16
17
 
@@ -182,15 +183,17 @@ Return ONLY valid JSON.`,
182
183
  for (const g of correctionGrades) {
183
184
  if (g.learned) {
184
185
  // Agent followed the correction unprompted — decay toward background (floor 3)
186
+ assertRecordId(g.id);
187
+ // Direct interpolation safe: assertRecordId validates format above
185
188
  await store.queryExec(
186
- `UPDATE type::record($gid) SET importance = math::max([3, importance - 2])`,
187
- { gid: g.id },
189
+ `UPDATE ${g.id} SET importance = math::max([3, importance - 2])`,
188
190
  ).catch(e => swallow.warn("cognitive-check:correctionDecay", e));
189
191
  } else {
190
192
  // Correction was relevant but agent ignored it — reinforce (cap 9)
193
+ assertRecordId(g.id);
194
+ // Direct interpolation safe: assertRecordId validates format above
191
195
  await store.queryExec(
192
- `UPDATE type::record($gid) SET importance = math::min([9, importance + 1])`,
193
- { gid: g.id },
196
+ `UPDATE ${g.id} SET importance = math::min([9, importance + 1])`,
194
197
  ).catch(e => swallow.warn("cognitive-check:correctionReinforce", e));
195
198
  }
196
199
  }
@@ -219,9 +222,11 @@ Return ONLY valid JSON.`,
219
222
  // Mid-session resolution — mark addressed memories immediately
220
223
  const resolvedGrades = result.grades.filter(g => g.resolved && g.id.startsWith("memory:"));
221
224
  for (const g of resolvedGrades) {
225
+ assertRecordId(g.id);
226
+ // Direct interpolation safe: assertRecordId validates format above
222
227
  await store.queryExec(
223
- `UPDATE type::record($gid) SET status = 'resolved', resolved_at = time::now(), resolved_by = $sid`,
224
- { gid: g.id, sid: params.sessionId },
228
+ `UPDATE ${g.id} SET status = 'resolved', resolved_at = time::now(), resolved_by = $sid`,
229
+ { sid: params.sessionId },
225
230
  ).catch(e => swallow.warn("cognitive-check:resolve", e));
226
231
  }
227
232
  } catch (e) {
@@ -317,9 +322,12 @@ async function applyRetrievalGrades(
317
322
  { id: grade.id, sid: sessionId },
318
323
  );
319
324
  if (row?.[0]?.id) {
325
+ const rid = String(row[0].id);
326
+ assertRecordId(rid);
327
+ // Direct interpolation safe: assertRecordId validates format above
320
328
  await store.queryExec(
321
- `UPDATE type::record($rid) SET llm_relevance = $score, llm_relevant = $relevant, llm_reason = $reason`,
322
- { rid: String(row[0].id), score: grade.score, relevant: grade.relevant, reason: grade.reason },
329
+ `UPDATE ${rid} SET llm_relevance = $score, llm_relevant = $relevant, llm_reason = $reason`,
330
+ { score: grade.score, relevant: grade.relevant, reason: grade.reason },
323
331
  );
324
332
  }
325
333
  // Feed relevance score into the utility cache — drives WMR provenUtility scoring
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Shared concept-extraction helpers.
3
+ *
4
+ * Regex-based extraction of concept names from text, plus helpers to
5
+ * upsert extracted concepts and link them via arbitrary edge types.
6
+ */
7
+
8
+ import type { SurrealStore } from "./surreal.js";
9
+ import type { EmbeddingService } from "./embeddings.js";
10
+ import { swallow } from "./errors.js";
11
+
12
+ // Same regexes used by the original extractAndLinkConcepts in context-engine.
13
+ export const CONCEPT_RE = /\b(?:(?:use|using|implement|create|add|configure|setup|install|import)\s+)([A-Z][a-zA-Z0-9_-]+(?:\s+[A-Z][a-zA-Z0-9_-]+)?)/g;
14
+ export const TECH_TERMS = /\b(api|database|schema|migration|endpoint|middleware|component|service|module|handler|controller|model|interface|type|class|function|method|hook|plugin|extension|config|cache|queue|worker|daemon)\b/gi;
15
+
16
+ /** Extract concept name strings from free text using regex heuristics. */
17
+ export function extractConceptNames(text: string): string[] {
18
+ const concepts = new Set<string>();
19
+
20
+ let match: RegExpExecArray | null;
21
+ const re1 = new RegExp(CONCEPT_RE.source, CONCEPT_RE.flags);
22
+ while ((match = re1.exec(text)) !== null) {
23
+ concepts.add(match[1].trim());
24
+ }
25
+
26
+ const re2 = new RegExp(TECH_TERMS.source, TECH_TERMS.flags);
27
+ while ((match = re2.exec(text)) !== null) {
28
+ concepts.add(match[1].toLowerCase());
29
+ }
30
+
31
+ return [...concepts].slice(0, 10);
32
+ }
33
+
34
+ /**
35
+ * Upsert concepts from text and link them to a source node via the given edge.
36
+ *
37
+ * Used for:
38
+ * - turn → "mentions" → concept (existing behaviour)
39
+ * - memory → "about_concept" → concept (Fix 1)
40
+ * - artifact → "artifact_mentions" → concept (Fix 2)
41
+ */
42
+ export async function upsertAndLinkConcepts(
43
+ sourceId: string,
44
+ edgeName: string,
45
+ text: string,
46
+ store: SurrealStore,
47
+ embeddings: EmbeddingService,
48
+ logTag: string,
49
+ ): Promise<void> {
50
+ const names = extractConceptNames(text);
51
+ if (names.length === 0) return;
52
+
53
+ for (const name of names) {
54
+ try {
55
+ let embedding: number[] | null = null;
56
+ if (embeddings.isAvailable()) {
57
+ try { embedding = await embeddings.embed(name); } catch { /* ok */ }
58
+ }
59
+ const conceptId = await store.upsertConcept(name, embedding);
60
+ if (conceptId) {
61
+ await store.relate(sourceId, edgeName, conceptId)
62
+ .catch(e => swallow(`${logTag}:relate`, e));
63
+ }
64
+ } catch (e) {
65
+ swallow(`${logTag}:upsert`, e);
66
+ }
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Link a newly-upserted concept to existing concepts via narrower/broader
72
+ * edges when one concept's name is a substring of the other (indicating a
73
+ * parent-child hierarchy, e.g. "React" → "React hooks").
74
+ */
75
+ export async function linkConceptHierarchy(
76
+ conceptId: string,
77
+ conceptName: string,
78
+ store: SurrealStore,
79
+ logTag: string,
80
+ ): Promise<void> {
81
+ try {
82
+ const existing = await store.queryFirst<{ id: string; content: string }>(
83
+ `SELECT id, content FROM concept WHERE id != $cid LIMIT 50`,
84
+ { cid: conceptId },
85
+ );
86
+ if (existing.length === 0) return;
87
+
88
+ const lowerName = conceptName.toLowerCase();
89
+
90
+ for (const other of existing) {
91
+ const otherLower = (other.content ?? "").toLowerCase();
92
+ if (!otherLower || otherLower === lowerName) continue;
93
+
94
+ const otherId = String(other.id);
95
+
96
+ if (lowerName.includes(otherLower) && lowerName !== otherLower) {
97
+ // New concept is more specific (e.g. "React hooks" contains "React")
98
+ await store.relate(conceptId, "narrower", otherId)
99
+ .catch(e => swallow(`${logTag}:narrower`, e));
100
+ await store.relate(otherId, "broader", conceptId)
101
+ .catch(e => swallow(`${logTag}:broader`, e));
102
+ } else if (otherLower.includes(lowerName) && otherLower !== lowerName) {
103
+ // New concept is more general (e.g. "React" contained in "React hooks")
104
+ await store.relate(conceptId, "broader", otherId)
105
+ .catch(e => swallow(`${logTag}:broader`, e));
106
+ await store.relate(otherId, "narrower", conceptId)
107
+ .catch(e => swallow(`${logTag}:narrower`, e));
108
+ }
109
+ }
110
+ } catch (e) {
111
+ swallow(`${logTag}:hierarchy`, e);
112
+ }
113
+ }
@@ -51,6 +51,7 @@ import { extractSkill } from "./skills.js";
51
51
  import { generateReflection } from "./reflection.js";
52
52
  import { graduateCausalToSkills } from "./skills.js";
53
53
  import { swallow } from "./errors.js";
54
+ import { upsertAndLinkConcepts } from "./concept-extract.js";
54
55
 
55
56
  const __dirname = dirname(fileURLToPath(import.meta.url));
56
57
 
@@ -305,6 +306,7 @@ export class KongBrainContextEngine implements ContextEngine {
305
306
  }
306
307
  } else {
307
308
  session.lastAssistantText = text;
309
+ if (turnId) session.lastAssistantTurnId = turnId;
308
310
  }
309
311
 
310
312
  return { ingested: true };
@@ -506,7 +508,11 @@ export class KongBrainContextEngine implements ContextEngine {
506
508
  if (embeddings.isAvailable()) {
507
509
  try { embedding = await embeddings.embed(handoffText); } catch { /* ok */ }
508
510
  }
509
- await store.createMemory(handoffText, embedding, 8, "handoff", session.sessionId);
511
+ const handoffMemId = await store.createMemory(handoffText, embedding, 8, "handoff", session.sessionId);
512
+ if (handoffMemId && session.surrealSessionId) {
513
+ await store.relate(handoffMemId, "summarizes", session.surrealSessionId)
514
+ .catch(e => swallow.warn("midCleanup:summarizes", e));
515
+ }
510
516
  }
511
517
  })().catch(e => swallow.warn("midCleanup:handoff", e)),
512
518
  );
@@ -548,44 +554,15 @@ function hasSemantic(text: string): boolean {
548
554
  return text.split(/\s+/).filter(w => w.length > 2).length >= 3;
549
555
  }
550
556
 
551
- // --- Concept extraction (shared with llm-output hook) ---
552
-
553
- const CONCEPT_RE = /\b(?:(?:use|using|implement|create|add|configure|setup|install|import)\s+)([A-Z][a-zA-Z0-9_-]+(?:\s+[A-Z][a-zA-Z0-9_-]+)?)/g;
554
- const TECH_TERMS = /\b(api|database|schema|migration|endpoint|middleware|component|service|module|handler|controller|model|interface|type|class|function|method|hook|plugin|extension|config|cache|queue|worker|daemon)\b/gi;
557
+ // --- Concept extraction (delegates to shared helper) ---
555
558
 
556
559
  async function extractAndLinkConcepts(
557
560
  turnId: string,
558
561
  text: string,
559
562
  state: GlobalPluginState,
560
563
  ): Promise<void> {
561
- const concepts = new Set<string>();
562
-
563
- let match: RegExpExecArray | null;
564
- const re1 = new RegExp(CONCEPT_RE.source, CONCEPT_RE.flags);
565
- while ((match = re1.exec(text)) !== null) {
566
- concepts.add(match[1].trim());
567
- }
568
-
569
- const re2 = new RegExp(TECH_TERMS.source, TECH_TERMS.flags);
570
- while ((match = re2.exec(text)) !== null) {
571
- concepts.add(match[1].toLowerCase());
572
- }
573
-
574
- if (concepts.size === 0) return;
575
-
576
- for (const conceptText of [...concepts].slice(0, 10)) {
577
- try {
578
- let embedding: number[] | null = null;
579
- if (state.embeddings.isAvailable()) {
580
- try { embedding = await state.embeddings.embed(conceptText); } catch { /* ok */ }
581
- }
582
- const conceptId = await state.store.upsertConcept(conceptText, embedding);
583
- if (conceptId) {
584
- await state.store.relate(turnId, "mentions", conceptId)
585
- .catch(e => swallow("concepts:relate", e));
586
- }
587
- } catch (e) {
588
- swallow("concepts:upsert", e);
589
- }
590
- }
564
+ await upsertAndLinkConcepts(
565
+ turnId, "mentions", text,
566
+ state.store, state.embeddings, "concepts",
567
+ );
591
568
  }
@@ -5,6 +5,7 @@
5
5
  import type { GlobalPluginState } from "../state.js";
6
6
  import { recordToolOutcome } from "../retrieval-quality.js";
7
7
  import { swallow } from "../errors.js";
8
+ import { upsertAndLinkConcepts } from "../concept-extract.js";
8
9
 
9
10
  export function createAfterToolCallHandler(state: GlobalPluginState) {
10
11
  return async (
@@ -30,13 +31,20 @@ export function createAfterToolCallHandler(state: GlobalPluginState) {
30
31
  ? event.result.slice(0, 500)
31
32
  : JSON.stringify(event.result ?? "").slice(0, 500);
32
33
 
34
+ let toolResultTurnId: string | undefined;
33
35
  try {
34
- await state.store.upsertTurn({
36
+ toolResultTurnId = await state.store.upsertTurn({
35
37
  session_id: session.sessionId,
36
38
  role: "tool",
37
39
  text: `[${event.toolName}] ${resultText}`,
38
40
  embedding: null,
39
41
  });
42
+
43
+ // Fix 5: Link tool result turn back to the assistant turn that triggered it
44
+ if (toolResultTurnId && session.lastAssistantTurnId) {
45
+ await state.store.relate(toolResultTurnId, "tool_result_of", session.lastAssistantTurnId)
46
+ .catch(e => swallow.warn("hook:afterToolCall:tool_result_of", e));
47
+ }
40
48
  } catch (e) {
41
49
  swallow("hook:afterToolCall:store", e);
42
50
  }
@@ -92,8 +100,15 @@ async function trackArtifact(
92
100
  const artifactId = await state.store.createArtifact(
93
101
  (args.path as string) ?? "shell", ext, description, emb,
94
102
  );
95
- if (artifactId && taskId) {
96
- await state.store.relate(taskId, "produced", artifactId)
97
- .catch(e => swallow.warn("artifact:relate", e));
103
+ if (artifactId) {
104
+ if (taskId) {
105
+ await state.store.relate(taskId, "produced", artifactId)
106
+ .catch(e => swallow.warn("artifact:relate", e));
107
+ }
108
+ // Fix 2: Link artifact to concepts it mentions
109
+ await upsertAndLinkConcepts(
110
+ artifactId, "artifact_mentions", description,
111
+ state.store, state.embeddings, "artifact:concepts",
112
+ );
98
113
  }
99
114
  }
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ import { createAfterToolCallHandler } from "./hooks/after-tool-call.js";
23
23
  import { createLlmOutputHandler } from "./hooks/llm-output.js";
24
24
  import { startMemoryDaemon } from "./daemon-manager.js";
25
25
  import { seedIdentity } from "./identity.js";
26
+ import { seedCognitiveBootstrap } from "./cognitive-bootstrap.js";
26
27
  import { synthesizeWakeup } from "./wakeup.js";
27
28
  import { extractSkill } from "./skills.js";
28
29
  import { generateReflection, setReflectionContextWindow } from "./reflection.js";
@@ -418,6 +419,13 @@ export default definePluginEntry({
418
419
  .then(n => { if (n > 0) logger.info(`Seeded ${n} identity chunks`); })
419
420
  .catch(e => swallow.warn("factory:seedIdentity", e));
420
421
 
422
+ seedCognitiveBootstrap(store, embeddings)
423
+ .then(r => {
424
+ if (r.identitySeeded > 0 || r.coreSeeded > 0)
425
+ logger.info(`Cognitive bootstrap: ${r.identitySeeded} identity + ${r.coreSeeded} core`);
426
+ })
427
+ .catch(e => swallow.warn("factory:seedBootstrap", e));
428
+
421
429
  return new KongBrainContextEngine(state);
422
430
  });
423
431
 
@@ -12,6 +12,8 @@ import type { TurnData, PriorExtractions } from "./daemon-types.js";
12
12
  import type { SurrealStore } from "./surreal.js";
13
13
  import type { EmbeddingService } from "./embeddings.js";
14
14
  import { swallow } from "./errors.js";
15
+ import { assertRecordId } from "./surreal.js";
16
+ import { upsertAndLinkConcepts, linkConceptHierarchy } from "./concept-extract.js";
15
17
 
16
18
  // --- Build the extraction prompt ---
17
19
 
@@ -172,10 +174,12 @@ export async function writeExtractionResults(
172
174
  writeOps.push((async () => {
173
175
  for (const memId of result.resolved!.slice(0, 20)) {
174
176
  if (typeof memId !== "string" || !RECORD_ID_RE.test(memId)) continue;
177
+ assertRecordId(memId);
175
178
  counts.resolved++;
179
+ // Direct interpolation safe: assertRecordId validates format above
176
180
  await store.queryExec(
177
- `UPDATE type::record($mid) SET status = 'resolved', resolved_at = time::now(), resolved_by = $sid`,
178
- { mid: memId, sid: sessionId },
181
+ `UPDATE ${memId} SET status = 'resolved', resolved_at = time::now(), resolved_by = $sid`,
182
+ { sid: sessionId },
179
183
  ).catch(e => swallow.warn("daemon:resolveMemory", e));
180
184
  }
181
185
  })());
@@ -193,7 +197,10 @@ export async function writeExtractionResults(
193
197
  if (embeddings.isAvailable()) {
194
198
  try { emb = await embeddings.embed(c.content); } catch (e) { swallow("daemon:embedConcept", e); }
195
199
  }
196
- await store.upsertConcept(c.content, emb, `daemon:${sessionId}`);
200
+ const conceptId = await store.upsertConcept(c.content, emb, `daemon:${sessionId}`);
201
+ if (conceptId) {
202
+ await linkConceptHierarchy(conceptId, c.name, store, "daemon:concept");
203
+ }
197
204
  })());
198
205
  }
199
206
  }
@@ -209,7 +216,10 @@ export async function writeExtractionResults(
209
216
  if (embeddings.isAvailable()) {
210
217
  try { emb = await embeddings.embed(text); } catch (e) { swallow("daemon:embedCorrection", e); }
211
218
  }
212
- await store.createMemory(text, emb, 9, "correction", sessionId);
219
+ const memId = await store.createMemory(text, emb, 9, "correction", sessionId);
220
+ if (memId) {
221
+ await upsertAndLinkConcepts(memId, "about_concept", text, store, embeddings, "daemon:correction");
222
+ }
213
223
  })());
214
224
  }
215
225
  }
@@ -225,7 +235,10 @@ export async function writeExtractionResults(
225
235
  if (embeddings.isAvailable()) {
226
236
  try { emb = await embeddings.embed(text); } catch (e) { swallow("daemon:embedPreference", e); }
227
237
  }
228
- await store.createMemory(text, emb, 7, "preference", sessionId);
238
+ const memId = await store.createMemory(text, emb, 7, "preference", sessionId);
239
+ if (memId) {
240
+ await upsertAndLinkConcepts(memId, "about_concept", text, store, embeddings, "daemon:preference");
241
+ }
229
242
  })());
230
243
  }
231
244
  }
@@ -243,7 +256,10 @@ export async function writeExtractionResults(
243
256
  if (embeddings.isAvailable()) {
244
257
  try { emb = await embeddings.embed(`${a.path} ${desc}`); } catch (e) { swallow("daemon:embedArtifact", e); }
245
258
  }
246
- await store.createArtifact(a.path, a.action ?? "modified", desc, emb);
259
+ const artId = await store.createArtifact(a.path, a.action ?? "modified", desc, emb);
260
+ if (artId) {
261
+ await upsertAndLinkConcepts(artId, "artifact_mentions", `${a.path} ${desc}`, store, embeddings, "daemon:artifact");
262
+ }
247
263
  })());
248
264
  }
249
265
  }
@@ -259,7 +275,10 @@ export async function writeExtractionResults(
259
275
  if (embeddings.isAvailable()) {
260
276
  try { emb = await embeddings.embed(text); } catch (e) { swallow("daemon:embedDecision", e); }
261
277
  }
262
- await store.createMemory(text, emb, 7, "decision", sessionId);
278
+ const memId = await store.createMemory(text, emb, 7, "decision", sessionId);
279
+ if (memId) {
280
+ await upsertAndLinkConcepts(memId, "about_concept", text, store, embeddings, "daemon:decision");
281
+ }
263
282
  })());
264
283
  }
265
284
  }
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 { assertRecordId } from "./surreal.js";
16
17
 
17
18
  // --- Types ---
18
19
 
@@ -237,12 +238,14 @@ export async function recordSkillOutcome(
237
238
 
238
239
  try {
239
240
  const field = success ? "success_count" : "failure_count";
241
+ assertRecordId(skillId);
242
+ // Direct interpolation safe: assertRecordId validates format above
240
243
  await store.queryExec(
241
- `UPDATE type::record($sid) SET
244
+ `UPDATE ${skillId} SET
242
245
  ${field} += 1,
243
246
  avg_duration_ms = (avg_duration_ms * (success_count + failure_count - 1) + $dur) / (success_count + failure_count),
244
247
  last_used = time::now()`,
245
- { sid: skillId, dur: durationMs },
248
+ { dur: durationMs },
246
249
  );
247
250
  } catch (e) { swallow("skills:non-critical", e); }
248
251
  }
package/src/state.ts CHANGED
@@ -38,6 +38,7 @@ export class SessionState {
38
38
 
39
39
  // Turn tracking
40
40
  lastUserTurnId = "";
41
+ lastAssistantTurnId = "";
41
42
  lastUserText = "";
42
43
  lastAssistantText = "";
43
44
  toolCallCount = 0;
package/src/surreal.ts CHANGED
@@ -447,10 +447,8 @@ export class SurrealStore {
447
447
  assertRecordId(fromId);
448
448
  assertRecordId(toId);
449
449
  const safeName = edge.replace(/[^a-zA-Z0-9_]/g, "");
450
- await this.queryExec(
451
- `RELATE type::record($from)->${safeName}->type::record($to)`,
452
- { from: fromId, to: toId },
453
- );
450
+ // Direct interpolation safe: assertRecordId validates format above
451
+ await this.queryExec(`RELATE ${fromId}->${safeName}->${toId}`);
454
452
  }
455
453
 
456
454
  // ── 5-Pillar entity operations ─────────────────────────────────────────
@@ -504,12 +502,12 @@ export class SurrealStore {
504
502
  ): Promise<void> {
505
503
  assertRecordId(sessionId);
506
504
  await this.queryExec(
507
- `UPDATE type::record($sid) SET
505
+ `UPDATE ${sessionId} SET
508
506
  turn_count += 1,
509
507
  total_input_tokens += $input,
510
508
  total_output_tokens += $output,
511
509
  last_active = time::now()`,
512
- { sid: sessionId, input: inputTokens, output: outputTokens },
510
+ { input: inputTokens, output: outputTokens },
513
511
  );
514
512
  }
515
513
 
@@ -517,27 +515,25 @@ export class SurrealStore {
517
515
  assertRecordId(sessionId);
518
516
  if (summary) {
519
517
  await this.queryExec(
520
- `UPDATE type::record($sid) SET ended_at = time::now(), summary = $summary`,
521
- { sid: sessionId, summary },
518
+ `UPDATE ${sessionId} SET ended_at = time::now(), summary = $summary`,
519
+ { summary },
522
520
  );
523
521
  } else {
524
- await this.queryExec(`UPDATE type::record($sid) SET ended_at = time::now()`, { sid: sessionId });
522
+ await this.queryExec(`UPDATE ${sessionId} SET ended_at = time::now()`);
525
523
  }
526
524
  }
527
525
 
528
526
  async markSessionActive(sessionId: string): Promise<void> {
529
527
  assertRecordId(sessionId);
530
528
  await this.queryExec(
531
- `UPDATE type::record($sid) SET cleanup_completed = false, last_active = time::now()`,
532
- { sid: sessionId },
529
+ `UPDATE ${sessionId} SET cleanup_completed = false, last_active = time::now()`,
533
530
  );
534
531
  }
535
532
 
536
533
  async markSessionEnded(sessionId: string): Promise<void> {
537
534
  assertRecordId(sessionId);
538
535
  await this.queryExec(
539
- `UPDATE type::record($sid) SET ended_at = time::now(), cleanup_completed = true`,
540
- { sid: sessionId },
536
+ `UPDATE ${sessionId} SET ended_at = time::now(), cleanup_completed = true`,
541
537
  );
542
538
  }
543
539
 
@@ -555,8 +551,7 @@ export class SurrealStore {
555
551
  assertRecordId(sessionId);
556
552
  assertRecordId(taskId);
557
553
  await this.queryExec(
558
- `RELATE type::record($from)->session_task->type::record($to)`,
559
- { from: sessionId, to: taskId },
554
+ `RELATE ${sessionId}->session_task->${taskId}`,
560
555
  );
561
556
  }
562
557
 
@@ -564,8 +559,7 @@ export class SurrealStore {
564
559
  assertRecordId(taskId);
565
560
  assertRecordId(projectId);
566
561
  await this.queryExec(
567
- `RELATE type::record($from)->task_part_of->type::record($to)`,
568
- { from: taskId, to: projectId },
562
+ `RELATE ${taskId}->task_part_of->${projectId}`,
569
563
  );
570
564
  }
571
565
 
@@ -573,8 +567,7 @@ export class SurrealStore {
573
567
  assertRecordId(agentId);
574
568
  assertRecordId(taskId);
575
569
  await this.queryExec(
576
- `RELATE type::record($from)->performed->type::record($to)`,
577
- { from: agentId, to: taskId },
570
+ `RELATE ${agentId}->performed->${taskId}`,
578
571
  );
579
572
  }
580
573
 
@@ -582,8 +575,7 @@ export class SurrealStore {
582
575
  assertRecordId(agentId);
583
576
  assertRecordId(projectId);
584
577
  await this.queryExec(
585
- `RELATE type::record($from)->owns->type::record($to)`,
586
- { from: agentId, to: projectId },
578
+ `RELATE ${agentId}->owns->${projectId}`,
587
579
  );
588
580
  }
589
581
 
@@ -625,7 +617,7 @@ export class SurrealStore {
625
617
  for (let hop = 0; hop < hops && frontier.length > 0; hop++) {
626
618
  const forwardQueries = frontier.flatMap((id) =>
627
619
  forwardEdges.map((edge) =>
628
- this.queryFirst<any>(`${selectFields} FROM type::record($nid)->${edge}->? LIMIT 3`, { ...bindings, nid: id }).catch(
620
+ this.queryFirst<any>(`${selectFields} FROM ${id}->${edge}->? LIMIT 3`, bindings).catch(
629
621
  (e) => {
630
622
  swallow.warn("surreal:graphExpand", e);
631
623
  return [] as Record<string, unknown>[];
@@ -636,7 +628,7 @@ export class SurrealStore {
636
628
 
637
629
  const reverseQueries = frontier.flatMap((id) =>
638
630
  reverseEdges.map((edge) =>
639
- this.queryFirst<any>(`${selectFields} FROM type::record($nid)<-${edge}<-? LIMIT 3`, { ...bindings, nid: id }).catch(
631
+ this.queryFirst<any>(`${selectFields} FROM ${id}<-${edge}<-? LIMIT 3`, bindings).catch(
640
632
  (e) => {
641
633
  swallow.warn("surreal:graphExpand", e);
642
634
  return [] as Record<string, unknown>[];
@@ -687,8 +679,7 @@ export class SurrealStore {
687
679
  try {
688
680
  assertRecordId(id);
689
681
  await this.queryExec(
690
- `UPDATE type::record($rid) SET access_count += 1, last_accessed = time::now()`,
691
- { rid: id },
682
+ `UPDATE ${id} SET access_count += 1, last_accessed = time::now()`,
692
683
  );
693
684
  } catch (e) {
694
685
  swallow.warn("surreal:bumpAccessCounts", e);
@@ -710,8 +701,7 @@ export class SurrealStore {
710
701
  if (rows.length > 0) {
711
702
  const id = String(rows[0].id);
712
703
  await this.queryExec(
713
- `UPDATE type::record($cid) SET access_count += 1, last_accessed = time::now()`,
714
- { cid: id },
704
+ `UPDATE ${id} SET access_count += 1, last_accessed = time::now()`,
715
705
  );
716
706
  return id;
717
707
  }
@@ -768,8 +758,8 @@ export class SurrealStore {
768
758
  const existing = dupes[0];
769
759
  const newImp = Math.max(existing.importance ?? 0, importance);
770
760
  await this.queryExec(
771
- `UPDATE type::record($eid) SET access_count += 1, importance = $imp, last_accessed = time::now()`,
772
- { eid: String(existing.id), imp: newImp },
761
+ `UPDATE ${String(existing.id)} SET access_count += 1, importance = $imp, last_accessed = time::now()`,
762
+ { imp: newImp },
773
763
  );
774
764
  return String(existing.id);
775
765
  }
@@ -844,7 +834,7 @@ export class SurrealStore {
844
834
  assertRecordId(id);
845
835
  const ALLOWED_FIELDS = new Set(["text", "category", "priority", "tier", "active"]);
846
836
  const sets: string[] = [];
847
- const bindings: Record<string, unknown> = { _rid: id };
837
+ const bindings: Record<string, unknown> = {};
848
838
  for (const [key, val] of Object.entries(fields)) {
849
839
  if (val !== undefined && ALLOWED_FIELDS.has(key)) {
850
840
  sets.push(`${key} = $${key}`);
@@ -854,7 +844,7 @@ export class SurrealStore {
854
844
  if (sets.length === 0) return false;
855
845
  sets.push("updated_at = time::now()");
856
846
  const rows = await this.queryFirst<{ id: string }>(
857
- `UPDATE type::record($_rid) SET ${sets.join(", ")} RETURN id`,
847
+ `UPDATE ${id} SET ${sets.join(", ")} RETURN id`,
858
848
  bindings,
859
849
  );
860
850
  return rows.length > 0;
@@ -863,8 +853,7 @@ export class SurrealStore {
863
853
  async deleteCoreMemory(id: string): Promise<void> {
864
854
  assertRecordId(id);
865
855
  await this.queryExec(
866
- `UPDATE type::record($rid) SET active = false, updated_at = time::now()`,
867
- { rid: id },
856
+ `UPDATE ${id} SET active = false, updated_at = time::now()`,
868
857
  );
869
858
  }
870
859
 
@@ -1013,9 +1002,9 @@ export class SurrealStore {
1013
1002
 
1014
1003
  async resolveMemory(memoryId: string): Promise<boolean> {
1015
1004
  try {
1005
+ assertRecordId(memoryId);
1016
1006
  await this.queryFirst(
1017
- `UPDATE type::record($id) SET status = 'resolved', resolved_at = time::now()`,
1018
- { id: memoryId },
1007
+ `UPDATE ${memoryId} SET status = 'resolved', resolved_at = time::now()`,
1019
1008
  );
1020
1009
  return true;
1021
1010
  } catch (e) {
@@ -1223,10 +1212,10 @@ export class SurrealStore {
1223
1212
  assertRecordId(String(keep));
1224
1213
  assertRecordId(String(drop));
1225
1214
  await this.queryExec(
1226
- `UPDATE type::record($kid) SET access_count += 1, importance = math::max([importance, $imp])`,
1227
- { kid: String(keep), imp: dupe.importance },
1215
+ `UPDATE ${String(keep)} SET access_count += 1, importance = math::max([importance, $imp])`,
1216
+ { imp: dupe.importance },
1228
1217
  );
1229
- await this.queryExec(`DELETE type::record($did)`, { did: String(drop) });
1218
+ await this.queryExec(`DELETE ${String(drop)}`);
1230
1219
  seen.add(String(drop));
1231
1220
  merged++;
1232
1221
  }
@@ -1252,8 +1241,8 @@ export class SurrealStore {
1252
1241
  const emb = await embedFn(mem.text);
1253
1242
  if (!emb) continue;
1254
1243
  await this.queryExec(
1255
- `UPDATE type::record($mid) SET embedding = $emb`,
1256
- { mid: String(mem.id), emb },
1244
+ `UPDATE ${String(mem.id)} SET embedding = $emb`,
1245
+ { emb },
1257
1246
  );
1258
1247
 
1259
1248
  const dupes = await this.queryFirst<{
@@ -1283,10 +1272,10 @@ export class SurrealStore {
1283
1272
  assertRecordId(String(keep));
1284
1273
  assertRecordId(String(drop));
1285
1274
  await this.queryExec(
1286
- `UPDATE type::record($kid) SET access_count += 1, importance = math::max([importance, $imp])`,
1287
- { kid: String(keep), imp: dupe.importance },
1275
+ `UPDATE ${String(keep)} SET access_count += 1, importance = math::max([importance, $imp])`,
1276
+ { imp: dupe.importance },
1288
1277
  );
1289
- await this.queryExec(`DELETE type::record($did)`, { did: String(drop) });
1278
+ await this.queryExec(`DELETE ${String(drop)}`);
1290
1279
  seen.add(String(drop));
1291
1280
  merged++;
1292
1281
  }
@@ -1387,8 +1376,8 @@ export class SurrealStore {
1387
1376
  ): Promise<void> {
1388
1377
  assertRecordId(checkpointId);
1389
1378
  await this.queryExec(
1390
- `UPDATE type::record($cpid) SET status = "complete", memory_id = $mid`,
1391
- { cpid: checkpointId, mid: memoryId },
1379
+ `UPDATE ${checkpointId} SET status = "complete", memory_id = $mid`,
1380
+ { mid: memoryId },
1392
1381
  );
1393
1382
  }
1394
1383