kongbrain 0.4.4 → 0.5.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/CHANGELOG.md CHANGED
@@ -2,6 +2,53 @@
2
2
 
3
3
  All notable changes to KongBrain are documented here.
4
4
 
5
+ ## [0.5.0] - 2026-04-25
6
+
7
+ Configurable embedding providers. Closes #1.
8
+
9
+ ### Features
10
+ - **Configurable embedding provider**: New `embedding.provider` config field. Options: `local` (BGE-M3 via node-llama-cpp, default and unchanged) or `openai-compat` (any OpenAI-compatible `/v1/embeddings` endpoint — OpenAI, Azure OpenAI, Together, Anyscale, vLLM, LM Studio, Ollama, DeepInfra, Fireworks).
11
+ - **OpenAI-compatible provider**: `fetch`-based, no SDK dependency. Batches inputs at 96/request, retries 429 + 5xx with exponential backoff and `Retry-After` honoring, hard-fails on 401/403/404 and `insufficient_quota` with clear error messages, verifies returned dimensionality matches config.
12
+ - **Per-row provider tagging**: Every vector-bearing table (`turn`, `concept`, `memory`, `artifact`, `identity_chunk`, `skill`, `reflection`, `monologue`) gets an `embedding_provider` column. Searches filter by the active provider so vectors from different models (different vector spaces) never mix in HNSW results.
13
+ - **Re-embed migration tool**: `npx kongbrain-reembed --from <provider-id> [--dry-run] [--tables …] [--batch …]`. Resumable on interruption (the WHERE filter naturally excludes processed rows). Reports per-table progress and estimated cost.
14
+ - **Startup mismatch warning**: Logs a clear notice (with row counts and migration command) when the configured provider does not match what is in the database.
15
+ - **Provider env overrides**: `KONGBRAIN_EMBED_PROVIDER` flips provider without editing config; `OPENAI_BASE_URL` overrides endpoint (matches the official OpenAI SDK convention); `embedding.openaiCompat.apiKeyEnv` names the env var holding the secret so keys never appear in config files.
16
+ - **Plugin manifest**: `openclaw.plugin.json` extended with `provider` / `dimensions` / `openaiCompat` schema and uiHints with inline help text.
17
+
18
+ ### Infrastructure
19
+ - **Idempotent schema migration + backfill**: All schema additions use `IF NOT EXISTS`. On first startup with this version, existing rows are tagged with `local-bge-m3`. Runs cleanly on every subsequent startup as a no-op.
20
+
21
+ ### Tests
22
+ - 439 → 469 tests. New: 17 OpenAI provider unit tests (success, batching, dim mismatch, retry, hard-fail), 4 config tests (provider, env overrides, fallback), 8 migration tests (full migrate, table filter, dry-run, blank-text, multi-batch, refusal, no-op, format), 4 backfill upgrade-path integration tests, plus 6 gated live tests (real OpenAI / real DB / real reembed) that skip in CI.
23
+
24
+ ### Documentation
25
+ - README and README.npm updated with embedding-provider comparison table, switching instructions for OpenAI / Ollama / vLLM, and migration command.
26
+
27
+ ### Upgrade notes
28
+ - **No action required for existing local BGE-M3 deployments.** The schema migration adds the new column and tags all existing rows as `local-bge-m3`. Search continues to work identically.
29
+ - **To switch providers**: set `embedding.provider: "openai-compat"` and `OPENAI_API_KEY`. On restart you will see a warning about rows in the old vector space. Run `npx kongbrain-reembed --from local-bge-m3 --dry-run` to estimate cost (~$0.04 per ~3,400 turns on text-embedding-3-small), then drop `--dry-run` to migrate. Resumable if interrupted.
30
+
31
+ ## [0.4.4] - 2026-04-04
32
+
33
+ ### Performance
34
+ - **WMR rebalance**: Cosine-dominant scoring weights, dampen access count feedback loop that was reinforcing already-popular memories.
35
+ - **Tag-boosted concept retrieval**: Surface topically relevant concepts even when embedding similarity alone misses them.
36
+
37
+ ### Bug Fixes
38
+ - **Empty LLM extraction responses**: `outputFormat` injected via pi-ai's `onPayload` hook caused Anthropic API to return 0 content blocks. Removed structured output from pi-ai code path; daemon's JSON parsing cascade handles free-text reliably.
39
+ - **`SELECT WHERE id IN $ids` binding**: Same silent no-op as `bumpAccessCounts` — SurrealDB string arrays don't resolve to record references. Fixed in `getSessionRetrievedMemories` and ACAN `fetchTrainingData`.
40
+ - **ACAN NaN/Infinity validation**: `loadWeights` now rejects corrupted weights (null, NaN, Infinity in bias, W_final, or spot-checked W_q/W_k rows).
41
+ - **Lazy daemon start**: If gateway restarts mid-session, `afterTurn` now starts the daemon on demand instead of silently skipping extraction.
42
+ - **`getOrCreateSession` in afterTurn**: Resumed sessions after gateway restart no longer return null.
43
+ - **Model object unwrapping**: `defaults.model` can be `{primary: "provider/model"}` — unwrap and split provider/model format.
44
+
45
+ ### Infrastructure
46
+ - **CI pipeline**: GitHub Actions with SurrealDB service container, Node 22, 439 tests (unit + integration).
47
+ - **PR checks**: Type checking + unit tests on all pull requests.
48
+
49
+ ### Tests
50
+ - 415 → 439 tests. New: ACAN NaN/Infinity validation (7), score stability/performance (3), `SELECT IN` integration test, additional integration coverage.
51
+
5
52
  ## [0.4.2] - 2026-04-03
6
53
 
7
54
  ### Performance
package/README.github.md CHANGED
@@ -11,7 +11,7 @@
11
11
  [![Node.js](https://img.shields.io/badge/Node.js-20+-339933?style=for-the-badge&logo=node.js&logoColor=white)](https://nodejs.org)
12
12
  [![SurrealDB](https://img.shields.io/badge/SurrealDB-3.0-ff00a0?style=for-the-badge&logo=surrealdb&logoColor=white)](https://surrealdb.com)
13
13
  [![OpenClaw](https://img.shields.io/badge/OpenClaw-Plugin-ff6b35?style=for-the-badge)](https://github.com/openclaw/openclaw)
14
- [![Tests](https://img.shields.io/badge/Tests-415_passing-brightgreen?style=for-the-badge&logo=vitest&logoColor=white)](https://vitest.dev)
14
+ [![Tests](https://img.shields.io/badge/Tests-469_passing-brightgreen?style=for-the-badge&logo=vitest&logoColor=white)](https://vitest.dev)
15
15
 
16
16
  **A graph-backed cognitive engine for [OpenClaw](https://github.com/openclaw/openclaw).**
17
17
 
@@ -121,7 +121,9 @@ openclaw tui
121
121
 
122
122
  That's it. KongBrain uses whatever LLM provider and model you already have configured in OpenClaw (Anthropic, OpenAI, Google, Ollama, whatever). No separate API keys needed for the brain itself.
123
123
 
124
- The BGE-M3 embedding model (~420MB) downloads automatically on first startup from [Hugging Face](https://huggingface.co/BAAI/bge-m3). All database tables and indexes are created automatically on first run. No manual setup required.
124
+ By default KongBrain runs the BGE-M3 embedding model locally via `node-llama-cpp` — the GGUF (~420MB) auto-downloads from [Hugging Face](https://huggingface.co/BAAI/bge-m3) on first startup. For high-traffic deployments the local model can become a bottleneck on serial embedding calls; in that case switch to any OpenAI-compatible API (real OpenAI, Azure OpenAI, Together, vLLM, LM Studio, Ollama) by changing one config field. See [Embedding Providers](#embedding-providers) below.
125
+
126
+ All database tables and indexes are created automatically on first run. No manual setup required.
125
127
 
126
128
  <details>
127
129
  <summary><strong>Configuration Options</strong></summary>
@@ -135,8 +137,12 @@ All options have sensible defaults. Override via plugin config or environment va
135
137
  | `surreal.pass` | `SURREAL_PASS` | (required) |
136
138
  | `surreal.ns` | `SURREAL_NS` | `kong` |
137
139
  | `surreal.db` | `SURREAL_DB` | `memory` |
138
- | `embedding.modelPath` | `KONGBRAIN_EMBEDDING_MODEL` | Auto-downloaded BGE-M3 Q4_K_M |
140
+ | `embedding.provider` | `KONGBRAIN_EMBED_PROVIDER` | `local` (or `openai-compat`) |
139
141
  | `embedding.dimensions` | - | `1024` |
142
+ | `embedding.modelPath` | `EMBED_MODEL_PATH` | Auto-downloaded BGE-M3 Q4_K_M |
143
+ | `embedding.openaiCompat.model` | - | `text-embedding-3-small` |
144
+ | `embedding.openaiCompat.baseURL` | `OPENAI_BASE_URL` | `https://api.openai.com/v1` |
145
+ | `embedding.openaiCompat.apiKeyEnv` | - | `OPENAI_API_KEY` |
140
146
 
141
147
  Full config example:
142
148
 
@@ -166,6 +172,50 @@ Full config example:
166
172
 
167
173
  </details>
168
174
 
175
+ ### Embedding Providers
176
+
177
+ | | `local` (default) | `openai-compat` |
178
+ |---|---|---|
179
+ | **Inference** | BGE-M3 GGUF via node-llama-cpp, in-process | HTTP POST to `/v1/embeddings` |
180
+ | **Cost** | Zero (~420MB model on disk, CPU inference) | Per-token API charges |
181
+ | **Throughput** | Serial; bottlenecks under high turn volume | High parallelism, batched at 96 inputs/request |
182
+ | **Network** | None required | Required |
183
+ | **Compatible servers** | n/a | OpenAI, Azure OpenAI, Together, Anyscale, vLLM, LM Studio, Ollama, DeepInfra, Fireworks |
184
+
185
+ Switching providers is safe by design. Every embedding written to the database is tagged with the provider that produced it (`embedding_provider` column). At search time, KongBrain only compares vectors that were produced by the active provider — vectors from a different provider live in a different vector space and would corrupt similarity scores if mixed.
186
+
187
+ When you switch from `local` to `openai-compat`, pre-existing rows stay in the database but become invisible to recall until re-embedded. Run the included migration tool to bring them into the new vector space:
188
+
189
+ ```bash
190
+ # Estimate cost first (no writes)
191
+ npx kongbrain-reembed --from local-bge-m3 --dry-run
192
+
193
+ # Then run for real (resumable on interruption)
194
+ npx kongbrain-reembed --from local-bge-m3
195
+ ```
196
+
197
+ Cost is small: text-embedding-3-small is $0.02 per 1M tokens; a typical 3,400-turn database costs ~$0.04 to re-embed.
198
+
199
+ #### Switching to OpenAI
200
+
201
+ ```bash
202
+ export KONGBRAIN_EMBED_PROVIDER=openai-compat
203
+ export OPENAI_API_KEY=sk-...
204
+ # Restart the plugin. Migration tool handles existing data.
205
+ ```
206
+
207
+ #### Switching to a local OpenAI-compatible server (Ollama, vLLM, LM Studio)
208
+
209
+ ```bash
210
+ # Ollama example — runs entirely locally, no API key needed
211
+ ollama pull nomic-embed-text
212
+ export OPENAI_BASE_URL=http://localhost:11434/v1
213
+ export OPENAI_API_KEY=ollama # any non-empty string; Ollama doesn't validate
214
+ export KONGBRAIN_EMBED_PROVIDER=openai-compat
215
+ ```
216
+
217
+ Set `embedding.dimensions` to match the server's native output dim. KongBrain verifies the returned vector size on every response and fails loudly if the server ignores the `dimensions` parameter — preventing silently corrupted indexes.
218
+
169
219
  ---
170
220
 
171
221
  ## Architecture
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![Node.js](https://img.shields.io/badge/Node.js-20+-339933?style=for-the-badge&logo=node.js&logoColor=white)](https://nodejs.org)
8
8
  [![SurrealDB](https://img.shields.io/badge/SurrealDB-3.0-ff00a0?style=for-the-badge&logo=surrealdb&logoColor=white)](https://surrealdb.com)
9
9
  [![OpenClaw](https://img.shields.io/badge/OpenClaw-Plugin-ff6b35?style=for-the-badge)](https://github.com/openclaw/openclaw)
10
- [![Tests](https://img.shields.io/badge/Tests-88_passing-brightgreen?style=for-the-badge&logo=vitest&logoColor=white)](https://vitest.dev)
10
+ [![Tests](https://img.shields.io/badge/Tests-469_passing-brightgreen?style=for-the-badge&logo=vitest&logoColor=white)](https://vitest.dev)
11
11
 
12
12
  **A graph-backed cognitive engine for [OpenClaw](https://github.com/openclaw/openclaw).**
13
13
 
@@ -111,7 +111,9 @@ openclaw tui
111
111
 
112
112
  That's it. KongBrain uses whatever LLM provider and model you already have configured in OpenClaw (Anthropic, OpenAI, Google, Ollama, whatever). No separate API keys needed for the brain itself.
113
113
 
114
- The BGE-M3 embedding model (~420MB) downloads automatically on first startup from [Hugging Face](https://huggingface.co/BAAI/bge-m3). All database tables and indexes are created automatically on first run. No manual setup required.
114
+ By default KongBrain runs the BGE-M3 embedding model locally via `node-llama-cpp` — the GGUF (~420MB) auto-downloads from [Hugging Face](https://huggingface.co/BAAI/bge-m3) on first startup. For high-traffic deployments the local model can become a bottleneck on serial embedding calls; in that case switch to any OpenAI-compatible API (real OpenAI, Azure OpenAI, Together, vLLM, LM Studio, Ollama) by changing one config field.
115
+
116
+ All database tables and indexes are created automatically on first run. No manual setup required.
115
117
 
116
118
  <details>
117
119
  <summary><strong>Configuration Options</strong></summary>
@@ -125,8 +127,12 @@ All options have sensible defaults. Override via plugin config or environment va
125
127
  | `surreal.pass` | `SURREAL_PASS` | (required) |
126
128
  | `surreal.ns` | `SURREAL_NS` | `kong` |
127
129
  | `surreal.db` | `SURREAL_DB` | `memory` |
128
- | `embedding.modelPath` | `KONGBRAIN_EMBEDDING_MODEL` | Auto-downloaded BGE-M3 Q4_K_M |
130
+ | `embedding.provider` | `KONGBRAIN_EMBED_PROVIDER` | `local` (or `openai-compat`) |
129
131
  | `embedding.dimensions` | - | `1024` |
132
+ | `embedding.modelPath` | `EMBED_MODEL_PATH` | Auto-downloaded BGE-M3 Q4_K_M |
133
+ | `embedding.openaiCompat.model` | - | `text-embedding-3-small` |
134
+ | `embedding.openaiCompat.baseURL` | `OPENAI_BASE_URL` | `https://api.openai.com/v1` |
135
+ | `embedding.openaiCompat.apiKeyEnv` | - | `OPENAI_API_KEY` |
130
136
 
131
137
  Full config example:
132
138
 
@@ -156,6 +162,26 @@ Full config example:
156
162
 
157
163
  </details>
158
164
 
165
+ ### Embedding Providers
166
+
167
+ | | `local` (default) | `openai-compat` |
168
+ |---|---|---|
169
+ | **Inference** | BGE-M3 GGUF via node-llama-cpp, in-process | HTTP POST to `/v1/embeddings` |
170
+ | **Cost** | Zero | Per-token API charges |
171
+ | **Throughput** | Serial; bottlenecks under high turn volume | High parallelism, batched at 96 inputs/request |
172
+ | **Compatible servers** | n/a | OpenAI, Azure OpenAI, Together, Anyscale, vLLM, LM Studio, Ollama, DeepInfra, Fireworks |
173
+
174
+ Every embedding is tagged with the provider that produced it. At search time, KongBrain only compares vectors from the active provider — vectors from a different provider live in a different vector space.
175
+
176
+ When you switch providers, run the included migration tool to re-embed pre-existing rows:
177
+
178
+ ```bash
179
+ npx kongbrain-reembed --from local-bge-m3 --dry-run # estimate cost
180
+ npx kongbrain-reembed --from local-bge-m3 # run for real (resumable)
181
+ ```
182
+
183
+ text-embedding-3-small costs ~$0.04 to re-embed a typical 3,400-turn database.
184
+
159
185
  ---
160
186
 
161
187
  ## Architecture
package/README.npm.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![Node.js](https://img.shields.io/badge/Node.js-20+-339933?style=for-the-badge&logo=node.js&logoColor=white)](https://nodejs.org)
8
8
  [![SurrealDB](https://img.shields.io/badge/SurrealDB-3.0-ff00a0?style=for-the-badge&logo=surrealdb&logoColor=white)](https://surrealdb.com)
9
9
  [![OpenClaw](https://img.shields.io/badge/OpenClaw-Plugin-ff6b35?style=for-the-badge)](https://github.com/openclaw/openclaw)
10
- [![Tests](https://img.shields.io/badge/Tests-88_passing-brightgreen?style=for-the-badge&logo=vitest&logoColor=white)](https://vitest.dev)
10
+ [![Tests](https://img.shields.io/badge/Tests-469_passing-brightgreen?style=for-the-badge&logo=vitest&logoColor=white)](https://vitest.dev)
11
11
 
12
12
  **A graph-backed cognitive engine for [OpenClaw](https://github.com/openclaw/openclaw).**
13
13
 
@@ -111,7 +111,9 @@ openclaw tui
111
111
 
112
112
  That's it. KongBrain uses whatever LLM provider and model you already have configured in OpenClaw (Anthropic, OpenAI, Google, Ollama, whatever). No separate API keys needed for the brain itself.
113
113
 
114
- The BGE-M3 embedding model (~420MB) downloads automatically on first startup from [Hugging Face](https://huggingface.co/BAAI/bge-m3). All database tables and indexes are created automatically on first run. No manual setup required.
114
+ By default KongBrain runs the BGE-M3 embedding model locally via `node-llama-cpp` — the GGUF (~420MB) auto-downloads from [Hugging Face](https://huggingface.co/BAAI/bge-m3) on first startup. For high-traffic deployments the local model can become a bottleneck on serial embedding calls; in that case switch to any OpenAI-compatible API (real OpenAI, Azure OpenAI, Together, vLLM, LM Studio, Ollama) by changing one config field.
115
+
116
+ All database tables and indexes are created automatically on first run. No manual setup required.
115
117
 
116
118
  <details>
117
119
  <summary><strong>Configuration Options</strong></summary>
@@ -125,8 +127,12 @@ All options have sensible defaults. Override via plugin config or environment va
125
127
  | `surreal.pass` | `SURREAL_PASS` | (required) |
126
128
  | `surreal.ns` | `SURREAL_NS` | `kong` |
127
129
  | `surreal.db` | `SURREAL_DB` | `memory` |
128
- | `embedding.modelPath` | `KONGBRAIN_EMBEDDING_MODEL` | Auto-downloaded BGE-M3 Q4_K_M |
130
+ | `embedding.provider` | `KONGBRAIN_EMBED_PROVIDER` | `local` (or `openai-compat`) |
129
131
  | `embedding.dimensions` | - | `1024` |
132
+ | `embedding.modelPath` | `EMBED_MODEL_PATH` | Auto-downloaded BGE-M3 Q4_K_M |
133
+ | `embedding.openaiCompat.model` | - | `text-embedding-3-small` |
134
+ | `embedding.openaiCompat.baseURL` | `OPENAI_BASE_URL` | `https://api.openai.com/v1` |
135
+ | `embedding.openaiCompat.apiKeyEnv` | - | `OPENAI_API_KEY` |
130
136
 
131
137
  Full config example:
132
138
 
@@ -156,6 +162,26 @@ Full config example:
156
162
 
157
163
  </details>
158
164
 
165
+ ### Embedding Providers
166
+
167
+ | | `local` (default) | `openai-compat` |
168
+ |---|---|---|
169
+ | **Inference** | BGE-M3 GGUF via node-llama-cpp, in-process | HTTP POST to `/v1/embeddings` |
170
+ | **Cost** | Zero | Per-token API charges |
171
+ | **Throughput** | Serial; bottlenecks under high turn volume | High parallelism, batched at 96 inputs/request |
172
+ | **Compatible servers** | n/a | OpenAI, Azure OpenAI, Together, Anyscale, vLLM, LM Studio, Ollama, DeepInfra, Fireworks |
173
+
174
+ Every embedding is tagged with the provider that produced it. At search time, KongBrain only compares vectors from the active provider — vectors from a different provider live in a different vector space.
175
+
176
+ When you switch providers, run the included migration tool to re-embed pre-existing rows:
177
+
178
+ ```bash
179
+ npx kongbrain-reembed --from local-bge-m3 --dry-run # estimate cost
180
+ npx kongbrain-reembed --from local-bge-m3 # run for real (resumable)
181
+ ```
182
+
183
+ text-embedding-3-small costs ~$0.04 to re-embed a typical 3,400-turn database.
184
+
159
185
  ---
160
186
 
161
187
  ## Architecture
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.4.4
4
+ version: 0.5.0
5
5
  homepage: https://github.com/42U/kongbrain
6
6
  metadata:
7
7
  openclaw:
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * kongbrain-reembed — re-embed migration CLI.
4
+ *
5
+ * Reads connection settings from the same env vars the plugin uses, plus
6
+ * KONGBRAIN_EMBED_PROVIDER / OPENAI_BASE_URL / etc. for the target
7
+ * provider. Migrates rows tagged with --from to the active provider's
8
+ * vector space.
9
+ *
10
+ * Usage:
11
+ * kongbrain-reembed --from local-bge-m3 [--dry-run] [--tables turn,memory] [--batch 256]
12
+ *
13
+ * Resumable: each batch flips embedding_provider so processed rows leave
14
+ * the FROM filter. Restarting after a crash continues from where it
15
+ * stopped.
16
+ */
17
+
18
+ import { parsePluginConfig } from "../src/config.js";
19
+ import { createEmbeddingService } from "../src/embeddings.js";
20
+ import { SurrealStore } from "../src/surreal.js";
21
+ import {
22
+ formatResult,
23
+ reembedAll,
24
+ VECTOR_TABLES,
25
+ type VectorTable,
26
+ } from "../src/migrate-reembed.js";
27
+
28
+ interface CliFlags {
29
+ from: string | null;
30
+ dryRun: boolean;
31
+ tables: VectorTable[] | null;
32
+ batch: number;
33
+ help: boolean;
34
+ }
35
+
36
+ function parseArgs(argv: string[]): CliFlags {
37
+ const flags: CliFlags = {
38
+ from: null,
39
+ dryRun: false,
40
+ tables: null,
41
+ batch: 256,
42
+ help: false,
43
+ };
44
+ for (let i = 0; i < argv.length; i++) {
45
+ const a = argv[i];
46
+ if (a === "--help" || a === "-h") flags.help = true;
47
+ else if (a === "--dry-run") flags.dryRun = true;
48
+ else if (a === "--from") flags.from = argv[++i] ?? null;
49
+ else if (a === "--batch") flags.batch = Number(argv[++i] ?? "256");
50
+ else if (a === "--tables") {
51
+ const list = (argv[++i] ?? "").split(",").map(s => s.trim()).filter(Boolean);
52
+ const valid: VectorTable[] = [];
53
+ for (const t of list) {
54
+ if ((VECTOR_TABLES as readonly string[]).includes(t)) valid.push(t as VectorTable);
55
+ else throw new Error(`Unknown table: ${t}. Valid: ${VECTOR_TABLES.join(", ")}`);
56
+ }
57
+ flags.tables = valid;
58
+ }
59
+ }
60
+ return flags;
61
+ }
62
+
63
+ const HELP = `kongbrain-reembed — migrate embeddings between providers
64
+
65
+ Required:
66
+ --from <provider-id> Provider tag to migrate FROM (e.g. local-bge-m3)
67
+
68
+ Optional:
69
+ --dry-run Count rows + estimate cost without writing
70
+ --tables turn,memory Only migrate these tables (default: all 8)
71
+ --batch <n> Rows per batch (default: 256)
72
+ --help Show this message
73
+
74
+ The TARGET provider is whatever the active EmbeddingService produces, set
75
+ via plugin config + env vars (KONGBRAIN_EMBED_PROVIDER, OPENAI_BASE_URL,
76
+ the API key env var named in embedding.openaiCompat.apiKeyEnv).
77
+
78
+ Resumability: each batch flips the embedding_provider tag, so re-running
79
+ after an interruption picks up from where the last successful batch left
80
+ off — no checkpoint file needed.
81
+
82
+ Example: migrate from local BGE-M3 to OpenAI text-embedding-3-small at 1024d:
83
+ export KONGBRAIN_EMBED_PROVIDER=openai-compat
84
+ export OPENAI_API_KEY=sk-...
85
+ npx kongbrain-reembed --from local-bge-m3 --dry-run # check the size
86
+ npx kongbrain-reembed --from local-bge-m3 # run for real
87
+ `;
88
+
89
+ async function main(): Promise<number> {
90
+ const flags = parseArgs(process.argv.slice(2));
91
+ if (flags.help) {
92
+ console.log(HELP);
93
+ return 0;
94
+ }
95
+ if (!flags.from) {
96
+ console.error("Missing required --from <provider-id>. See --help.");
97
+ return 2;
98
+ }
99
+
100
+ const config = parsePluginConfig();
101
+ const store = new SurrealStore(config.surreal);
102
+ const embeddings = createEmbeddingService(config.embedding);
103
+
104
+ console.log(`Source provider: ${flags.from}`);
105
+ console.log(`Target provider: ${embeddings.providerId}`);
106
+ console.log(`SurrealDB: ${config.surreal.url}`);
107
+ console.log(`Mode: ${flags.dryRun ? "DRY RUN" : "WRITE"}`);
108
+ console.log("");
109
+
110
+ await store.initialize();
111
+ store.setActiveProvider(embeddings.providerId);
112
+ if (!flags.dryRun) {
113
+ await embeddings.initialize();
114
+ }
115
+
116
+ try {
117
+ const result = await reembedAll(store, embeddings, {
118
+ fromProvider: flags.from,
119
+ tables: flags.tables ?? undefined,
120
+ batchSize: flags.batch,
121
+ dryRun: flags.dryRun,
122
+ onProgress: ev => {
123
+ process.stdout.write(
124
+ `[${ev.table}] ${ev.tableProcessed}/${ev.tableTotal}\r`,
125
+ );
126
+ },
127
+ });
128
+ process.stdout.write("\n");
129
+ console.log(formatResult(result, embeddings.providerId));
130
+ return 0;
131
+ } finally {
132
+ await embeddings.dispose().catch(() => {});
133
+ await store.close().catch(() => {});
134
+ }
135
+ }
136
+
137
+ main().then(
138
+ code => process.exit(code),
139
+ err => {
140
+ console.error("kongbrain-reembed failed:", err?.message ?? err);
141
+ process.exit(1);
142
+ },
143
+ );
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "kongbrain",
3
3
  "name": "KongBrain",
4
- "description": "Graph-backed cognitive context engine with SurrealDB + BGE-M3",
4
+ "description": "Graph-backed cognitive context engine with SurrealDB + pluggable embeddings (local BGE-M3 or OpenAI-compatible)",
5
5
  "kind": "context-engine",
6
6
  "requires": {
7
7
  "bins": ["surreal"],
@@ -30,16 +30,36 @@
30
30
  "label": "SurrealDB Database",
31
31
  "placeholder": "memory"
32
32
  },
33
- "embedding.modelPath": {
34
- "label": "Embedding Model Path",
35
- "placeholder": "~/.node-llama-cpp/models/bge-m3-q4_k_m.gguf",
36
- "help": "Path to BGE-M3 GGUF model file (~420MB, auto-downloaded if missing)",
37
- "advanced": true
33
+ "embedding.provider": {
34
+ "label": "Embedding Provider",
35
+ "placeholder": "local",
36
+ "help": "Either 'local' (BGE-M3 via node-llama-cpp) or 'openai-compat' (any OpenAI-compatible /v1/embeddings endpoint: OpenAI, Azure, Together, vLLM, Ollama, etc.)"
38
37
  },
39
38
  "embedding.dimensions": {
40
39
  "label": "Embedding Dimensions",
41
40
  "placeholder": "1024",
41
+ "help": "Output vector dimensionality. Must match across providers if you intend to swap them. text-embedding-3-* honors arbitrary values; non-OpenAI compat servers may return their native dim regardless."
42
+ },
43
+ "embedding.modelPath": {
44
+ "label": "Local Model Path",
45
+ "placeholder": "~/.node-llama-cpp/models/bge-m3-q4_k_m.gguf",
46
+ "help": "Path to BGE-M3 GGUF file. Only used when provider is 'local'.",
42
47
  "advanced": true
48
+ },
49
+ "embedding.openaiCompat.model": {
50
+ "label": "OpenAI-compat Model",
51
+ "placeholder": "text-embedding-3-small",
52
+ "help": "Model name passed in the embeddings request body. Only used when provider is 'openai-compat'."
53
+ },
54
+ "embedding.openaiCompat.baseURL": {
55
+ "label": "OpenAI-compat Base URL",
56
+ "placeholder": "https://api.openai.com/v1",
57
+ "help": "Endpoint base. The OPENAI_BASE_URL env var overrides this if set. Only used when provider is 'openai-compat'."
58
+ },
59
+ "embedding.openaiCompat.apiKeyEnv": {
60
+ "label": "API Key Env Var",
61
+ "placeholder": "OPENAI_API_KEY",
62
+ "help": "Name of the env var holding the API key (the secret stays out of config files). Only used when provider is 'openai-compat'."
43
63
  }
44
64
  },
45
65
  "configSchema": {
@@ -61,8 +81,18 @@
61
81
  "type": "object",
62
82
  "additionalProperties": false,
63
83
  "properties": {
84
+ "provider": { "type": "string", "enum": ["local", "openai-compat"] },
85
+ "dimensions": { "type": "number" },
64
86
  "modelPath": { "type": "string" },
65
- "dimensions": { "type": "number" }
87
+ "openaiCompat": {
88
+ "type": "object",
89
+ "additionalProperties": false,
90
+ "properties": {
91
+ "model": { "type": "string" },
92
+ "baseURL": { "type": "string" },
93
+ "apiKeyEnv": { "type": "string" }
94
+ }
95
+ }
66
96
  }
67
97
  }
68
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kongbrain",
3
- "version": "0.4.4",
3
+ "version": "0.5.0",
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",
@@ -24,6 +24,9 @@
24
24
  "knowledge-graph",
25
25
  "llm"
26
26
  ],
27
+ "bin": {
28
+ "kongbrain-reembed": "./bin/kongbrain-reembed.ts"
29
+ },
27
30
  "openclaw": {
28
31
  "extensions": [
29
32
  "./src/index.ts"
package/src/causal.ts CHANGED
@@ -127,9 +127,12 @@ export async function queryCausalContext(
127
127
  const seen = new Set<string>(validIds);
128
128
  let frontier = validIds;
129
129
  const results: VectorSearchResult[] = [];
130
- const bindings = { vec: queryVec };
130
+ // Score only against rows in the active provider's vector space; rows from
131
+ // other providers still appear via graph traversal but score 0.
132
+ const bindings = { vec: queryVec, provider: store.getActiveProvider() };
131
133
 
132
134
  const scoreExpr = `, IF embedding != NONE AND array::len(embedding) > 0
135
+ AND embedding_provider = $provider
133
136
  THEN vector::similarity::cosine(embedding, $vec)
134
137
  ELSE 0 END AS score`;
135
138
 
@@ -146,6 +146,7 @@ export async function seedCognitiveBootstrap(
146
146
  chunk_index: i,
147
147
  text: chunk.text,
148
148
  embedding: vec,
149
+ embedding_provider: embeddings.providerId,
149
150
  importance: chunk.importance,
150
151
  },
151
152
  });
@@ -108,9 +108,10 @@ export async function linkToRelevantConcepts(
108
108
  `SELECT id, vector::similarity::cosine(embedding, $vec) AS score
109
109
  FROM concept
110
110
  WHERE embedding != NONE AND array::len(embedding) > 0
111
+ AND embedding_provider = $provider
111
112
  ORDER BY score DESC
112
113
  LIMIT $lim`,
113
- { vec, lim: limit },
114
+ { vec, lim: limit, provider: embeddings.providerId },
114
115
  );
115
116
  for (const m of matches) {
116
117
  if (m.score < threshold) break;
@@ -175,9 +176,10 @@ export async function linkConceptHierarchy(
175
176
  FROM concept
176
177
  WHERE id != $cid
177
178
  AND embedding != NONE AND array::len(embedding) > 0
179
+ AND embedding_provider = $provider
178
180
  ORDER BY score DESC
179
181
  LIMIT 3`,
180
- { vec: conceptEmb, cid: conceptId },
182
+ { vec: conceptEmb, cid: conceptId, provider: embeddings.providerId },
181
183
  );
182
184
  for (const s of similar) {
183
185
  if (s.score < 0.75) break;
package/src/config.ts CHANGED
@@ -10,9 +10,26 @@ export interface SurrealConfig {
10
10
  db: string;
11
11
  }
12
12
 
13
+ export type EmbeddingProvider = "local" | "openai-compat";
14
+
15
+ export interface OpenAICompatEmbeddingConfig {
16
+ /** Model name passed in the embeddings request body (e.g. "text-embedding-3-small"). */
17
+ model: string;
18
+ /** Endpoint base URL. Default: "https://api.openai.com/v1". */
19
+ baseURL: string;
20
+ /** Name of the env var holding the API key. Default: "OPENAI_API_KEY". */
21
+ apiKeyEnv: string;
22
+ }
23
+
13
24
  export interface EmbeddingConfig {
14
- modelPath: string;
25
+ /** Which provider to use. Default "local" (BGE-M3 via node-llama-cpp). */
26
+ provider: EmbeddingProvider;
27
+ /** Vector dimensionality the active provider should produce. */
15
28
  dimensions: number;
29
+ /** Path to the local GGUF model — only consulted when provider === "local". */
30
+ modelPath: string;
31
+ /** OpenAI-compatible provider settings — only consulted when provider === "openai-compat". */
32
+ openaiCompat: OpenAICompatEmbeddingConfig;
16
33
  }
17
34
 
18
35
  export interface ThresholdConfig {
@@ -34,6 +51,43 @@ export interface KongBrainConfig {
34
51
  thresholds: ThresholdConfig;
35
52
  }
36
53
 
54
+ function parseEmbeddingConfig(raw: Record<string, unknown>): EmbeddingConfig {
55
+ const openaiCompatRaw = (raw.openaiCompat ?? {}) as Record<string, unknown>;
56
+
57
+ // Provider precedence: env var > plugin config > default "local"
58
+ const rawProvider =
59
+ process.env.KONGBRAIN_EMBED_PROVIDER ??
60
+ (typeof raw.provider === "string" ? raw.provider : null);
61
+ const provider: EmbeddingProvider =
62
+ rawProvider === "openai-compat" ? "openai-compat" : "local";
63
+
64
+ return {
65
+ provider,
66
+ dimensions: typeof raw.dimensions === "number" ? raw.dimensions : 1024,
67
+ modelPath:
68
+ process.env.EMBED_MODEL_PATH ??
69
+ (typeof raw.modelPath === "string"
70
+ ? raw.modelPath
71
+ : join(homedir(), ".node-llama-cpp", "models", "bge-m3-q4_k_m.gguf")),
72
+ openaiCompat: {
73
+ model:
74
+ typeof openaiCompatRaw.model === "string"
75
+ ? openaiCompatRaw.model
76
+ : "text-embedding-3-small",
77
+ // baseURL: env wins (matches the official openai SDK convention)
78
+ baseURL:
79
+ process.env.OPENAI_BASE_URL ??
80
+ (typeof openaiCompatRaw.baseURL === "string"
81
+ ? openaiCompatRaw.baseURL
82
+ : "https://api.openai.com/v1"),
83
+ apiKeyEnv:
84
+ typeof openaiCompatRaw.apiKeyEnv === "string"
85
+ ? openaiCompatRaw.apiKeyEnv
86
+ : "OPENAI_API_KEY",
87
+ },
88
+ };
89
+ }
90
+
37
91
  /**
38
92
  * Parse plugin config from openclaw.plugin.json configSchema values,
39
93
  * with env var overrides and sensible defaults.
@@ -66,15 +120,7 @@ export function parsePluginConfig(raw?: Record<string, unknown>): KongBrainConfi
66
120
  ns: (typeof surreal.ns === "string" ? surreal.ns : null) ?? process.env.SURREAL_NS ?? "kong",
67
121
  db: (typeof surreal.db === "string" ? surreal.db : null) ?? process.env.SURREAL_DB ?? "memory",
68
122
  },
69
- embedding: {
70
- modelPath:
71
- process.env.EMBED_MODEL_PATH ??
72
- (typeof embedding.modelPath === "string"
73
- ? embedding.modelPath
74
- : join(homedir(), ".node-llama-cpp", "models", "bge-m3-q4_k_m.gguf")),
75
- dimensions:
76
- typeof embedding.dimensions === "number" ? embedding.dimensions : 1024,
77
- },
123
+ embedding: parseEmbeddingConfig(embedding),
78
124
  thresholds: {
79
125
  daemonTokenThreshold:
80
126
  typeof thresholds.daemonTokenThreshold === "number" ? thresholds.daemonTokenThreshold : 4000,