kongbrain 0.3.12 → 0.3.14
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/README.github.md +7 -7
- package/README.md +7 -7
- package/README.npm.md +7 -7
- package/SKILL.md +7 -8
- package/openclaw.plugin.json +4 -0
- package/package.json +1 -1
- package/scripts/backfill-embeddings.ts +74 -0
- package/src/concept-extract.ts +42 -0
- package/src/hooks/after-tool-call.ts +3 -3
- package/src/memory-daemon.ts +16 -22
- package/src/skills.ts +3 -3
- package/src/surreal.ts +11 -3
package/README.github.md
CHANGED
|
@@ -60,15 +60,13 @@ npm install -g openclaw
|
|
|
60
60
|
|
|
61
61
|
Install SurrealDB via your platform's package manager (see [surrealdb.com/install](https://surrealdb.com/docs/surrealdb/installation)):
|
|
62
62
|
|
|
63
|
+
macOS:
|
|
63
64
|
```bash
|
|
64
|
-
# macOS
|
|
65
65
|
brew install surrealdb/tap/surreal
|
|
66
|
-
|
|
67
|
-
# Linux (Debian/Ubuntu)
|
|
68
|
-
curl -sSf https://install.surrealdb.com | sh
|
|
69
|
-
export PATH="$HOME/.surrealdb:$PATH"
|
|
70
66
|
```
|
|
71
67
|
|
|
68
|
+
Linux — see `https://surrealdb.com/docs/surrealdb/installation` for your distro.
|
|
69
|
+
|
|
72
70
|
Then start it locally, **change the credentials before use**:
|
|
73
71
|
|
|
74
72
|
```bash
|
|
@@ -88,11 +86,13 @@ docker run -d --name surrealdb -p 127.0.0.1:8042:8000 \
|
|
|
88
86
|
|
|
89
87
|
### 3. Install KongBrain
|
|
90
88
|
|
|
89
|
+
From ClawHub (recommended):
|
|
91
90
|
```bash
|
|
92
|
-
# From ClawHub (recommended)
|
|
93
91
|
openclaw plugins install clawhub:kongbrain
|
|
92
|
+
```
|
|
94
93
|
|
|
95
|
-
|
|
94
|
+
From npm:
|
|
95
|
+
```bash
|
|
96
96
|
openclaw plugins install kongbrain
|
|
97
97
|
```
|
|
98
98
|
|
package/README.md
CHANGED
|
@@ -50,15 +50,13 @@ npm install -g openclaw
|
|
|
50
50
|
|
|
51
51
|
Install SurrealDB via your platform's package manager (see [surrealdb.com/install](https://surrealdb.com/docs/surrealdb/installation)):
|
|
52
52
|
|
|
53
|
+
macOS:
|
|
53
54
|
```bash
|
|
54
|
-
# macOS
|
|
55
55
|
brew install surrealdb/tap/surreal
|
|
56
|
-
|
|
57
|
-
# Linux (Debian/Ubuntu)
|
|
58
|
-
curl -sSf https://install.surrealdb.com | sh
|
|
59
|
-
export PATH="$HOME/.surrealdb:$PATH"
|
|
60
56
|
```
|
|
61
57
|
|
|
58
|
+
Linux — see `https://surrealdb.com/docs/surrealdb/installation` for your distro.
|
|
59
|
+
|
|
62
60
|
Then start it locally — **change the credentials before use**:
|
|
63
61
|
|
|
64
62
|
```bash
|
|
@@ -78,11 +76,13 @@ docker run -d --name surrealdb -p 127.0.0.1:8042:8000 \
|
|
|
78
76
|
|
|
79
77
|
### 3. Install KongBrain
|
|
80
78
|
|
|
79
|
+
From ClawHub (recommended):
|
|
81
80
|
```bash
|
|
82
|
-
# From ClawHub (recommended)
|
|
83
81
|
openclaw plugins install clawhub:kongbrain
|
|
82
|
+
```
|
|
84
83
|
|
|
85
|
-
|
|
84
|
+
From npm:
|
|
85
|
+
```bash
|
|
86
86
|
openclaw plugins install kongbrain
|
|
87
87
|
```
|
|
88
88
|
|
package/README.npm.md
CHANGED
|
@@ -50,15 +50,13 @@ npm install -g openclaw
|
|
|
50
50
|
|
|
51
51
|
Install SurrealDB via your platform's package manager (see [surrealdb.com/install](https://surrealdb.com/docs/surrealdb/installation)):
|
|
52
52
|
|
|
53
|
+
macOS:
|
|
53
54
|
```bash
|
|
54
|
-
# macOS
|
|
55
55
|
brew install surrealdb/tap/surreal
|
|
56
|
-
|
|
57
|
-
# Linux (Debian/Ubuntu)
|
|
58
|
-
curl -sSf https://install.surrealdb.com | sh
|
|
59
|
-
export PATH="$HOME/.surrealdb:$PATH"
|
|
60
56
|
```
|
|
61
57
|
|
|
58
|
+
Linux — see `https://surrealdb.com/docs/surrealdb/installation` for your distro.
|
|
59
|
+
|
|
62
60
|
Then start it locally — **change the credentials before use**:
|
|
63
61
|
|
|
64
62
|
```bash
|
|
@@ -78,11 +76,13 @@ docker run -d --name surrealdb -p 127.0.0.1:8042:8000 \
|
|
|
78
76
|
|
|
79
77
|
### 3. Install KongBrain
|
|
80
78
|
|
|
79
|
+
From ClawHub (recommended):
|
|
81
80
|
```bash
|
|
82
|
-
# From ClawHub (recommended)
|
|
83
81
|
openclaw plugins install clawhub:kongbrain
|
|
82
|
+
```
|
|
84
83
|
|
|
85
|
-
|
|
84
|
+
From npm:
|
|
85
|
+
```bash
|
|
86
86
|
openclaw plugins install kongbrain
|
|
87
87
|
```
|
|
88
88
|
|
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.14
|
|
5
5
|
homepage: https://github.com/42U/kongbrain
|
|
6
6
|
metadata:
|
|
7
7
|
openclaw:
|
|
@@ -48,23 +48,22 @@ KongBrain gives your OpenClaw agent persistent, structured memory:
|
|
|
48
48
|
|
|
49
49
|
See the official install guide: https://surrealdb.com/docs/surrealdb/installation
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
macOS:
|
|
53
52
|
```bash
|
|
54
|
-
# macOS
|
|
55
53
|
brew install surrealdb/tap/surreal
|
|
54
|
+
```
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
curl -sSf https://install.surrealdb.com | sh
|
|
56
|
+
Linux — see `https://surrealdb.com/docs/surrealdb/installation` for your distro.
|
|
59
57
|
|
|
60
|
-
|
|
58
|
+
Docker:
|
|
59
|
+
```bash
|
|
61
60
|
docker pull surrealdb/surrealdb:latest
|
|
62
61
|
```
|
|
63
62
|
|
|
64
63
|
### Start SurrealDB
|
|
65
64
|
|
|
65
|
+
Local only (recommended) — use strong credentials in production:
|
|
66
66
|
```bash
|
|
67
|
-
# Local only (recommended) - use strong credentials in production
|
|
68
67
|
surreal start --user youruser --pass yourpass --bind 127.0.0.1:8000 surrealkv:~/.kongbrain/surreal.db
|
|
69
68
|
```
|
|
70
69
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
"name": "KongBrain",
|
|
4
4
|
"description": "Graph-backed cognitive context engine with SurrealDB + BGE-M3",
|
|
5
5
|
"kind": "context-engine",
|
|
6
|
+
"requires": {
|
|
7
|
+
"bins": ["surreal"],
|
|
8
|
+
"env": ["SURREAL_URL", "SURREAL_USER", "SURREAL_PASS", "SURREAL_NS", "SURREAL_DB"]
|
|
9
|
+
},
|
|
6
10
|
"uiHints": {
|
|
7
11
|
"surreal.url": {
|
|
8
12
|
"label": "SurrealDB URL",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kongbrain",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
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",
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* One-shot backfill: embed all concepts that have `content` but no embedding vector.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* cd /home/zero/voidorigin/kongbrain
|
|
7
|
+
* npx tsx scripts/backfill-embeddings.ts
|
|
8
|
+
*
|
|
9
|
+
* Env vars (all have defaults matching the plugin config):
|
|
10
|
+
* SURREAL_URL (default: ws://localhost:8042/rpc)
|
|
11
|
+
* SURREAL_USER (default: root)
|
|
12
|
+
* SURREAL_PASS (default: root)
|
|
13
|
+
* SURREAL_NS (default: kong)
|
|
14
|
+
* SURREAL_DB (default: memory)
|
|
15
|
+
* EMBED_MODEL_PATH (default: ~/.node-llama-cpp/models/bge-m3-q4_k_m.gguf)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { parsePluginConfig } from "../src/config.js";
|
|
19
|
+
import { SurrealStore } from "../src/surreal.js";
|
|
20
|
+
import { EmbeddingService } from "../src/embeddings.js";
|
|
21
|
+
|
|
22
|
+
async function main() {
|
|
23
|
+
const config = parsePluginConfig();
|
|
24
|
+
const store = new SurrealStore(config.surreal);
|
|
25
|
+
const embeddings = new EmbeddingService(config.embedding);
|
|
26
|
+
|
|
27
|
+
console.log("[backfill] Connecting to SurrealDB...");
|
|
28
|
+
await store.initialize();
|
|
29
|
+
|
|
30
|
+
console.log("[backfill] Loading embedding model...");
|
|
31
|
+
await embeddings.initialize();
|
|
32
|
+
|
|
33
|
+
// Find concepts with content but no embedding
|
|
34
|
+
const bare = await store.queryFirst<{ id: string; content: string }>(
|
|
35
|
+
`SELECT id, content FROM concept
|
|
36
|
+
WHERE content IS NOT NONE AND content != ''
|
|
37
|
+
AND (embedding IS NONE OR array::len(embedding) = 0)`,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
console.log(`[backfill] Found ${bare.length} concepts needing embeddings.`);
|
|
41
|
+
if (bare.length === 0) {
|
|
42
|
+
console.log("[backfill] Nothing to do.");
|
|
43
|
+
await embeddings.dispose();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let ok = 0;
|
|
48
|
+
let fail = 0;
|
|
49
|
+
|
|
50
|
+
for (const concept of bare) {
|
|
51
|
+
const id = String(concept.id);
|
|
52
|
+
try {
|
|
53
|
+
const vec = await embeddings.embed(concept.content);
|
|
54
|
+
await store.queryExec(
|
|
55
|
+
`UPDATE ${id} SET embedding = $emb`,
|
|
56
|
+
{ emb: vec },
|
|
57
|
+
);
|
|
58
|
+
ok++;
|
|
59
|
+
if (ok % 10 === 0) console.log(`[backfill] ${ok}/${bare.length} done...`);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
fail++;
|
|
62
|
+
console.error(`[backfill] Failed ${id}: ${e}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(`[backfill] Complete. Embedded: ${ok}, Failed: ${fail}`);
|
|
67
|
+
await embeddings.dispose();
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
main().catch((e) => {
|
|
72
|
+
console.error("[backfill] Fatal:", e);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
});
|
package/src/concept-extract.ts
CHANGED
|
@@ -79,6 +79,48 @@ export async function upsertAndLinkConcepts(
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Embedding-based concept linking — replaces batch-local linkToConcepts.
|
|
84
|
+
*
|
|
85
|
+
* Given a source node (memory, artifact, turn, skill) and its text content,
|
|
86
|
+
* embeds the text and finds the top-N most similar concepts in the graph,
|
|
87
|
+
* then creates edges from source → concept via the specified relation.
|
|
88
|
+
*
|
|
89
|
+
* This ensures linking works even when relevant concepts were created in
|
|
90
|
+
* prior batches or sessions — no batch-timing dependency.
|
|
91
|
+
*/
|
|
92
|
+
export async function linkToRelevantConcepts(
|
|
93
|
+
sourceId: string,
|
|
94
|
+
edgeName: string,
|
|
95
|
+
text: string,
|
|
96
|
+
store: SurrealStore,
|
|
97
|
+
embeddings: EmbeddingService,
|
|
98
|
+
logTag: string,
|
|
99
|
+
limit = 5,
|
|
100
|
+
threshold = 0.65,
|
|
101
|
+
): Promise<void> {
|
|
102
|
+
if (!embeddings.isAvailable() || !text) return;
|
|
103
|
+
try {
|
|
104
|
+
const vec = await embeddings.embed(text);
|
|
105
|
+
if (!vec?.length) return;
|
|
106
|
+
const matches = await store.queryFirst<{ id: string; score: number }>(
|
|
107
|
+
`SELECT id, vector::similarity::cosine(embedding, $vec) AS score
|
|
108
|
+
FROM concept
|
|
109
|
+
WHERE embedding != NONE AND array::len(embedding) > 0
|
|
110
|
+
ORDER BY score DESC
|
|
111
|
+
LIMIT $lim`,
|
|
112
|
+
{ vec, lim: limit },
|
|
113
|
+
);
|
|
114
|
+
for (const m of matches) {
|
|
115
|
+
if (m.score < threshold) break;
|
|
116
|
+
await store.relate(sourceId, edgeName, String(m.id))
|
|
117
|
+
.catch(e => swallow(`${logTag}:relate`, e));
|
|
118
|
+
}
|
|
119
|
+
} catch (e) {
|
|
120
|
+
swallow(`${logTag}:embed`, e);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
82
124
|
/**
|
|
83
125
|
* Link a newly-upserted concept to existing concepts via narrower/broader
|
|
84
126
|
* edges when one concept's name is a substring of the other (indicating a
|
|
@@ -5,7 +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 {
|
|
8
|
+
import { linkToRelevantConcepts } from "../concept-extract.js";
|
|
9
9
|
|
|
10
10
|
export function createAfterToolCallHandler(state: GlobalPluginState) {
|
|
11
11
|
return async (
|
|
@@ -128,8 +128,8 @@ async function trackArtifact(
|
|
|
128
128
|
await state.store.relate(artifactId, "used_in", projectId)
|
|
129
129
|
.catch(e => swallow.warn("artifact:used_in", e));
|
|
130
130
|
}
|
|
131
|
-
// Link artifact to concepts it mentions
|
|
132
|
-
await
|
|
131
|
+
// Link artifact to concepts it mentions (embedding-based similarity)
|
|
132
|
+
await linkToRelevantConcepts(
|
|
133
133
|
artifactId, "artifact_mentions", description,
|
|
134
134
|
state.store, state.embeddings, "artifact:concepts",
|
|
135
135
|
);
|
package/src/memory-daemon.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { SurrealStore } from "./surreal.js";
|
|
|
13
13
|
import type { EmbeddingService } from "./embeddings.js";
|
|
14
14
|
import { swallow } from "./errors.js";
|
|
15
15
|
import { assertRecordId } from "./surreal.js";
|
|
16
|
-
import { upsertAndLinkConcepts, linkConceptHierarchy } from "./concept-extract.js";
|
|
16
|
+
import { upsertAndLinkConcepts, linkConceptHierarchy, linkToRelevantConcepts } from "./concept-extract.js";
|
|
17
17
|
|
|
18
18
|
// --- Build the extraction prompt ---
|
|
19
19
|
|
|
@@ -170,29 +170,23 @@ export async function writeExtractionResults(
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
// ── Phase 2: Create mentions edges (turn → concept)
|
|
174
|
-
//
|
|
173
|
+
// ── Phase 2: Create mentions edges (turn → concept) via embedding similarity ─
|
|
174
|
+
// Each turn's text is embedded and matched against existing concepts in the
|
|
175
|
+
// graph. This replaces the old batch-local linking that only worked when
|
|
176
|
+
// concepts and turns were extracted in the same batch.
|
|
175
177
|
|
|
176
|
-
if (
|
|
177
|
-
const turnIds = turns.
|
|
178
|
-
for (const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
178
|
+
if (turns && turns.length > 0) {
|
|
179
|
+
const turnIds = turns.filter(t => t.turnId && t.text).slice(0, 15);
|
|
180
|
+
for (const t of turnIds) {
|
|
181
|
+
await linkToRelevantConcepts(
|
|
182
|
+
t.turnId!, "mentions", t.text!,
|
|
183
|
+
store, embeddings, "daemon:mentions", 5, 0.65,
|
|
184
|
+
);
|
|
183
185
|
}
|
|
184
186
|
}
|
|
185
187
|
|
|
186
188
|
// ── Phase 3: All other extractions in parallel ───────────────────────
|
|
187
189
|
|
|
188
|
-
/** Link a source node to all extracted concepts via the given edge. */
|
|
189
|
-
const linkToConcepts = async (sourceId: string, edgeName: string) => {
|
|
190
|
-
for (const conceptId of extractedConceptIds) {
|
|
191
|
-
await store.relate(sourceId, edgeName, conceptId)
|
|
192
|
-
.catch(e => swallow(`daemon:${edgeName}`, e));
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
|
|
196
190
|
const writeOps: Promise<void>[] = [];
|
|
197
191
|
|
|
198
192
|
// 1. Causal chains
|
|
@@ -260,7 +254,7 @@ export async function writeExtractionResults(
|
|
|
260
254
|
}
|
|
261
255
|
const memId = await store.createMemory(text, emb, 9, "correction", sessionId);
|
|
262
256
|
if (memId) {
|
|
263
|
-
await
|
|
257
|
+
await linkToRelevantConcepts(memId, "about_concept", text, store, embeddings, "daemon:correction:about_concept");
|
|
264
258
|
}
|
|
265
259
|
})());
|
|
266
260
|
}
|
|
@@ -279,7 +273,7 @@ export async function writeExtractionResults(
|
|
|
279
273
|
}
|
|
280
274
|
const memId = await store.createMemory(text, emb, 7, "preference", sessionId);
|
|
281
275
|
if (memId) {
|
|
282
|
-
await
|
|
276
|
+
await linkToRelevantConcepts(memId, "about_concept", text, store, embeddings, "daemon:preference:about_concept");
|
|
283
277
|
}
|
|
284
278
|
})());
|
|
285
279
|
}
|
|
@@ -300,7 +294,7 @@ export async function writeExtractionResults(
|
|
|
300
294
|
}
|
|
301
295
|
const artId = await store.createArtifact(a.path, a.action ?? "modified", desc, emb);
|
|
302
296
|
if (artId) {
|
|
303
|
-
await
|
|
297
|
+
await linkToRelevantConcepts(artId, "artifact_mentions", `${a.path} ${desc}`, store, embeddings, "daemon:artifact:artifact_mentions");
|
|
304
298
|
// used_in: artifact → project
|
|
305
299
|
if (projectId) {
|
|
306
300
|
await store.relate(artId, "used_in", projectId)
|
|
@@ -324,7 +318,7 @@ export async function writeExtractionResults(
|
|
|
324
318
|
}
|
|
325
319
|
const memId = await store.createMemory(text, emb, 7, "decision", sessionId);
|
|
326
320
|
if (memId) {
|
|
327
|
-
await
|
|
321
|
+
await linkToRelevantConcepts(memId, "about_concept", text, store, embeddings, "daemon:decision:about_concept");
|
|
328
322
|
}
|
|
329
323
|
})());
|
|
330
324
|
}
|
package/src/skills.ts
CHANGED
|
@@ -13,7 +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 {
|
|
16
|
+
import { linkToRelevantConcepts } from "./concept-extract.js";
|
|
17
17
|
import { assertRecordId } from "./surreal.js";
|
|
18
18
|
|
|
19
19
|
// --- Types ---
|
|
@@ -122,7 +122,7 @@ export async function extractSkill(
|
|
|
122
122
|
await supersedeOldSkills(skillId, skillEmb ?? [], store);
|
|
123
123
|
// skill_uses_concept: skill → concept
|
|
124
124
|
const skillDesc = `${parsed.name} ${parsed.description ?? ""} ${(parsed.preconditions ?? "")}`;
|
|
125
|
-
await
|
|
125
|
+
await linkToRelevantConcepts(skillId, "skill_uses_concept", skillDesc, store, embeddings, "skills:concepts");
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
return skillId || null;
|
|
@@ -334,7 +334,7 @@ export async function graduateCausalToSkills(
|
|
|
334
334
|
await supersedeOldSkills(gradSkillId, skillEmb ?? [], store);
|
|
335
335
|
// skill_uses_concept: skill → concept
|
|
336
336
|
const skillDesc = `${parsed.name} ${parsed.description ?? ""}`;
|
|
337
|
-
await
|
|
337
|
+
await linkToRelevantConcepts(gradSkillId, "skill_uses_concept", skillDesc, store, embeddings, "skills:graduate:concepts");
|
|
338
338
|
created++;
|
|
339
339
|
}
|
|
340
340
|
}
|
package/src/surreal.ts
CHANGED
|
@@ -692,9 +692,17 @@ export class SurrealStore {
|
|
|
692
692
|
);
|
|
693
693
|
if (rows.length > 0) {
|
|
694
694
|
const id = String(rows[0].id);
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
695
|
+
// Backfill embedding if the existing concept is missing one
|
|
696
|
+
if (embedding?.length) {
|
|
697
|
+
await this.queryExec(
|
|
698
|
+
`UPDATE ${id} SET access_count += 1, last_accessed = time::now(), embedding = IF embedding IS NONE OR array::len(embedding) = 0 THEN $emb ELSE embedding END`,
|
|
699
|
+
{ emb: embedding },
|
|
700
|
+
);
|
|
701
|
+
} else {
|
|
702
|
+
await this.queryExec(
|
|
703
|
+
`UPDATE ${id} SET access_count += 1, last_accessed = time::now()`,
|
|
704
|
+
);
|
|
705
|
+
}
|
|
698
706
|
return id;
|
|
699
707
|
}
|
|
700
708
|
const emb = embedding?.length ? embedding : undefined;
|