@dikolab/kbdb 0.5.0 → 0.6.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/CLA.md +68 -0
- package/LICENSE +676 -12
- package/LICENSING.md +58 -0
- package/README.md +47 -24
- package/README.md.bak +320 -0
- package/dist/README.md +320 -0
- package/dist/{chunk-BJXEVALU.mjs → chunk-QLJ33C74.mjs} +44 -44
- package/dist/chunk-QLJ33C74.mjs.map +7 -0
- package/dist/cli.cjs +550 -232
- package/dist/cli.cjs.map +4 -4
- package/dist/cli.d.ts +0 -8
- package/dist/cli.mjs +483 -154
- package/dist/cli.mjs.map +4 -4
- package/dist/kbdb-worker.cjs +36 -5
- package/dist/kbdb-worker.cjs.map +3 -3
- package/dist/mod.cjs +89 -30
- package/dist/mod.cjs.map +4 -4
- package/dist/mod.mjs +88 -30
- package/dist/mod.mjs.map +4 -4
- package/dist/src/cli.d.ts +0 -8
- package/dist/src/shared/cli/constants/defaults.constant.d.ts +1 -1
- package/dist/src/shared/cli/constants/recfile-command-field-map.constant.d.ts +8 -0
- package/dist/src/shared/cli/constants/recfile-field-maps.constant.d.ts +21 -0
- package/dist/src/shared/cli/functions/compute-directory-level.function.d.ts +14 -0
- package/dist/src/shared/cli/functions/dispatch-command.function.d.ts +2 -28
- package/dist/src/shared/cli/functions/format-command-output.function.d.ts +14 -0
- package/dist/src/shared/cli/functions/format-db-not-found.function.d.ts +20 -0
- package/dist/src/shared/cli/functions/format-output.function.d.ts +1 -0
- package/dist/src/shared/cli/functions/format-recfile-output.function.d.ts +11 -0
- package/dist/src/shared/cli/functions/render-recfile-value.function.d.ts +11 -0
- package/dist/src/shared/cli/functions/resolve-db-dir.function.d.ts +24 -0
- package/dist/src/shared/cli/functions/resolve-recfile-key.function.d.ts +9 -0
- package/dist/src/shared/cli/functions/run-db-path.function.d.ts +10 -0
- package/dist/src/shared/cli/functions/run-export.function.d.ts +8 -4
- package/dist/src/shared/cli/functions/run-import.function.d.ts +4 -0
- package/dist/src/shared/cli/functions/run-learn.function.d.ts +12 -0
- package/dist/src/shared/cli/index.d.ts +4 -1
- package/dist/src/shared/cli/typings/cli-options.interface.d.ts +9 -2
- package/dist/src/shared/cli/typings/db-resolution.interface.d.ts +9 -0
- package/dist/src/shared/cli/typings/recfile-field-map.type.d.ts +10 -0
- package/dist/src/shared/version/constants/version.constant.d.ts +1 -1
- package/dist/src/shared/wasm-codec/functions/decode-option-u8.function.d.ts +9 -0
- package/dist/src/shared/wasm-codec/functions/decode-section-records.function.d.ts +4 -0
- package/dist/src/shared/wasm-codec/functions/encode-add-section-params.function.d.ts +2 -45
- package/dist/src/shared/wasm-codec/functions/encode-update-section-params.function.d.ts +10 -1
- package/dist/src/shared/wasm-codec/index.d.ts +1 -1
- package/dist/src/shared/wasm-codec/typings/add-section-input.model.d.ts +6 -0
- package/dist/src/shared/wasm-codec/typings/section-record-trailing.model.d.ts +5 -0
- package/dist/src/shared/wasm-codec/typings/section-record.model.d.ts +5 -0
- package/dist/src/shared/worker-client/functions/build-deno-permissions.function.d.ts +11 -0
- package/dist/src/shared/worker-client/functions/read-crash-reason.function.d.ts +9 -0
- package/dist/src/shared/worker-client/functions/resolve-spawn-script.function.d.ts +6 -0
- package/dist/src/shared/worker-client/functions/spawn-daemon.function.d.ts +1 -1
- package/dist/src/shared/worker-client/typings/section.model.d.ts +5 -0
- package/dist/src/shared/worker-client/typings/worker-client-options.interface.d.ts +0 -6
- package/dist/src/shared/worker-daemon/functions/count-files.function.d.ts +12 -0
- package/dist/src/shared/worker-daemon/functions/handle-import.function.d.ts +2 -0
- package/dist/src/shared/worker-daemon/functions/handle-read-ops.function.d.ts +8 -0
- package/dist/src/shared/worker-daemon/functions/validate-daemon-path.function.d.ts +15 -0
- package/dist/src/worker.d.ts +0 -7
- package/dist/wasm/default-embedding/kbdb_default_embedding_bg.wasm +0 -0
- package/dist/wasm/default-embedding/package.json +1 -1
- package/dist/wasm/fs-database/kbdb_fs_database_bg.wasm +0 -0
- package/dist/wasm/fs-database/package.json +1 -1
- package/dist/wasm/fs-migration/package.json +1 -1
- package/dist/wasm/kb-worker/kbdb_worker_bg.wasm +0 -0
- package/dist/wasm/kb-worker/package.json +1 -1
- package/dist/wasm/query-parser/kbdb_query_parser_bg.wasm +0 -0
- package/dist/wasm/query-parser/package.json +1 -1
- package/dist/worker.d.ts +0 -7
- package/dist/worker.mjs +37 -6
- package/dist/worker.mjs.map +3 -3
- package/package.json +10 -9
- package/dist/chunk-BJXEVALU.mjs.map +0 -7
- package/dist/src/shared/cli/functions/resolve-raw-db-path.function.d.ts +0 -10
- package/docs/details/README.md +0 -30
- package/docs/details/agent-tooling.md +0 -132
- package/docs/details/cli.md +0 -777
- package/docs/details/knowledge-base.md +0 -180
- package/docs/details/library-api.md +0 -495
- package/docs/details/mcp-server.md +0 -482
- package/docs/details/search-and-ranking.md +0 -575
- package/docs/details/storage.md +0 -531
- package/docs/goals/agents.md +0 -751
- package/docs/goals/architecture.svg +0 -198
- package/docs/goals/auto-capture.md +0 -378
- package/docs/goals/benchmarking.md +0 -296
- package/docs/goals/cli.md +0 -2654
- package/docs/goals/concurrency.md +0 -259
- package/docs/goals/content-composer.md +0 -609
- package/docs/goals/content-parser.md +0 -279
- package/docs/goals/database.md +0 -1679
- package/docs/goals/document.md +0 -368
- package/docs/goals/hybrid-search.md +0 -465
- package/docs/goals/mcp.md +0 -1541
- package/docs/goals/overview.md +0 -124
- package/docs/goals/query-parser.md +0 -373
- package/docs/goals/query-result.md +0 -860
- package/docs/goals/semantic-dedup.md +0 -233
- package/docs/goals/skills.md +0 -810
- package/docs/goals/sync.md +0 -217
- package/docs/goals/worker-client.md +0 -1005
- package/docs/goals/worker-daemon.md +0 -1547
- package/docs/modules/overview.md +0 -319
- package/docs/modules/rust/embedding/module.md +0 -91
- package/docs/modules/rust/fs-database/functions/document.md +0 -48
- package/docs/modules/rust/fs-database/functions/integrity.md +0 -79
- package/docs/modules/rust/fs-database/functions/io.md +0 -87
- package/docs/modules/rust/fs-database/functions/module.md +0 -22
- package/docs/modules/rust/fs-database/functions/query-exec.md +0 -44
- package/docs/modules/rust/fs-database/functions/section.md +0 -76
- package/docs/modules/rust/fs-database/indexes/btree.md +0 -35
- package/docs/modules/rust/fs-database/indexes/corpus.md +0 -61
- package/docs/modules/rust/fs-database/indexes/inverted.md +0 -60
- package/docs/modules/rust/fs-database/indexes/module.md +0 -64
- package/docs/modules/rust/fs-database/indexes/positional.md +0 -39
- package/docs/modules/rust/fs-database/indexes/vectors.md +0 -110
- package/docs/modules/rust/fs-database/module.md +0 -150
- package/docs/modules/rust/fs-database/types/catalog.md +0 -53
- package/docs/modules/rust/fs-database/types/module.md +0 -12
- package/docs/modules/rust/fs-database/types/section.md +0 -127
- package/docs/modules/rust/kb-worker/functions/compose.md +0 -57
- package/docs/modules/rust/kb-worker/functions/module.md +0 -20
- package/docs/modules/rust/kb-worker/functions/retrieve.md +0 -194
- package/docs/modules/rust/kb-worker/functions/search.md +0 -173
- package/docs/modules/rust/kb-worker/functions/write.md +0 -172
- package/docs/modules/rust/kb-worker/module.md +0 -171
- package/docs/modules/rust/kb-worker/types/cache.md +0 -90
- package/docs/modules/rust/kb-worker/types/context.md +0 -26
- package/docs/modules/rust/kb-worker/types/module.md +0 -16
- package/docs/modules/rust/kb-worker/types/status.md +0 -29
- package/docs/modules/rust/kb-worker/types/token.md +0 -50
- package/docs/modules/rust/overview.md +0 -531
- package/docs/modules/rust/query-parser/functions/extract.md +0 -56
- package/docs/modules/rust/query-parser/functions/module.md +0 -18
- package/docs/modules/rust/query-parser/functions/parse.md +0 -33
- package/docs/modules/rust/query-parser/functions/pipeline.md +0 -9
- package/docs/modules/rust/query-parser/functions/rank.md +0 -85
- package/docs/modules/rust/query-parser/module.md +0 -131
- package/docs/modules/rust/query-parser/types/cache.md +0 -39
- package/docs/modules/rust/query-parser/types/keyphrase.md +0 -71
- package/docs/modules/rust/query-parser/types/module.md +0 -18
- package/docs/modules/rust/query-parser/types/token.md +0 -46
- package/docs/modules/rust/shared/content-composer/functions.md +0 -114
- package/docs/modules/rust/shared/content-composer/module.md +0 -147
- package/docs/modules/rust/shared/content-composer/types.md +0 -93
- package/docs/modules/rust/shared/content-parser/functions.md +0 -72
- package/docs/modules/rust/shared/content-parser/module.md +0 -99
- package/docs/modules/rust/shared/content-parser/types.md +0 -71
- package/docs/modules/rust/shared/corpus/module.md +0 -24
- package/docs/modules/rust/shared/corpus/types.md +0 -141
- package/docs/modules/rust/shared/document/module.md +0 -25
- package/docs/modules/rust/shared/document/types.md +0 -154
- package/docs/modules/rust/shared/encode/module.md +0 -22
- package/docs/modules/rust/shared/encode/traits.md +0 -304
- package/docs/modules/rust/shared/error/module.md +0 -28
- package/docs/modules/rust/shared/error/types.md +0 -302
- package/docs/modules/rust/shared/kbid/module.md +0 -24
- package/docs/modules/rust/shared/kbid/types.md +0 -147
- package/docs/modules/rust/shared/memory/functions.md +0 -209
- package/docs/modules/rust/shared/memory/module.md +0 -24
- package/docs/modules/rust/shared/module.md +0 -196
- package/docs/modules/rust/shared/pipeline/functions.md +0 -141
- package/docs/modules/rust/shared/pipeline/module.md +0 -62
- package/docs/modules/rust/shared/pipeline/types.md +0 -43
- package/docs/modules/rust/shared/query/module.md +0 -27
- package/docs/modules/rust/shared/query/types.md +0 -236
- package/docs/modules/rust/shared/section/module.md +0 -25
- package/docs/modules/rust/shared/section/types.md +0 -294
- package/docs/modules/rust/shared/term/module.md +0 -25
- package/docs/modules/rust/shared/term/types.md +0 -139
- package/docs/modules/typescript/cli.md +0 -131
- package/docs/modules/typescript/kbdb-worker.md +0 -150
- package/docs/modules/typescript/overview.md +0 -400
- package/docs/modules/typescript/shared/auto-capture/module.md +0 -73
- package/docs/modules/typescript/shared/cli/constants.md +0 -23
- package/docs/modules/typescript/shared/cli/functions.md +0 -809
- package/docs/modules/typescript/shared/cli/module.md +0 -138
- package/docs/modules/typescript/shared/cli/typings.md +0 -197
- package/docs/modules/typescript/shared/embedding/functions.md +0 -200
- package/docs/modules/typescript/shared/embedding/module.md +0 -77
- package/docs/modules/typescript/shared/embedding/typings.md +0 -124
- package/docs/modules/typescript/shared/hash/functions.md +0 -20
- package/docs/modules/typescript/shared/hash/module.md +0 -21
- package/docs/modules/typescript/shared/log/constants.md +0 -82
- package/docs/modules/typescript/shared/log/functions.md +0 -131
- package/docs/modules/typescript/shared/log/module.md +0 -153
- package/docs/modules/typescript/shared/log/typings.md +0 -82
- package/docs/modules/typescript/shared/mcp/classes.md +0 -179
- package/docs/modules/typescript/shared/mcp/functions.md +0 -386
- package/docs/modules/typescript/shared/mcp/module.md +0 -160
- package/docs/modules/typescript/shared/mcp/typings.md +0 -529
- package/docs/modules/typescript/shared/module.md +0 -110
- package/docs/modules/typescript/shared/platform/functions.md +0 -42
- package/docs/modules/typescript/shared/platform/module.md +0 -25
- package/docs/modules/typescript/shared/runtime/functions.md +0 -26
- package/docs/modules/typescript/shared/runtime/module.md +0 -27
- package/docs/modules/typescript/shared/runtime/typings.md +0 -13
- package/docs/modules/typescript/shared/version/module.md +0 -27
- package/docs/modules/typescript/shared/wasm-codec/functions.md +0 -391
- package/docs/modules/typescript/shared/wasm-codec/module.md +0 -98
- package/docs/modules/typescript/shared/worker-client/classes.md +0 -264
- package/docs/modules/typescript/shared/worker-client/functions.md +0 -175
- package/docs/modules/typescript/shared/worker-client/module.md +0 -92
- package/docs/modules/typescript/shared/worker-client/typings.md +0 -511
- package/docs/modules/typescript/shared/worker-daemon/classes.md +0 -239
- package/docs/modules/typescript/shared/worker-daemon/constants.md +0 -37
- package/docs/modules/typescript/shared/worker-daemon/functions.md +0 -234
- package/docs/modules/typescript/shared/worker-daemon/module.md +0 -118
- package/docs/modules/typescript/shared/worker-daemon/typings.md +0 -132
- package/docs/overview.md +0 -191
- package/docs/release-notes/0.1.0.md +0 -189
- package/docs/release-notes/0.1.1.md +0 -46
- package/docs/release-notes/0.1.2.md +0 -38
- package/docs/release-notes/0.1.3.md +0 -42
- package/docs/release-notes/0.2.0.md +0 -147
- package/docs/release-notes/0.3.0.md +0 -89
- package/docs/release-notes/0.3.1.md +0 -121
- package/docs/release-notes/0.3.2.md +0 -64
- package/docs/release-notes/0.4.0.md +0 -80
- package/docs/release-notes/0.4.1.md +0 -149
- package/docs/release-notes/0.4.3.md +0 -60
- package/docs/release-notes/0.5.0.md +0 -41
- package/docs/release-notes/README.md +0 -16
|
@@ -1,465 +0,0 @@
|
|
|
1
|
-
# Hybrid Search -- Lexical + Vector Retrieval
|
|
2
|
-
|
|
3
|
-
*Optional vector retrieval path alongside BM25F, fused
|
|
4
|
-
with RRF for best-of-both ranking.*
|
|
5
|
-
|
|
6
|
-
## Goal
|
|
7
|
-
|
|
8
|
-
The current retrieval mode is lexical search: BM25F with
|
|
9
|
-
stemming and synonym expansion. It works well for
|
|
10
|
-
structured documentation where terminology is consistent
|
|
11
|
-
-- the agent searches "authentication" and the indexed
|
|
12
|
-
content uses the same word.
|
|
13
|
-
|
|
14
|
-
Lexical search has a ceiling, however. It misses
|
|
15
|
-
associative recall when terminology diverges between the
|
|
16
|
-
time content was stored and the time it is retrieved.
|
|
17
|
-
Example: an agent stores "we chose Postgres because of
|
|
18
|
-
JSONB" and later searches "database rationale" -- lexical
|
|
19
|
-
search misses it because none of the indexed tokens
|
|
20
|
-
(`postgres`, `chose`, `jsonb`) overlap with the query
|
|
21
|
-
tokens (`database`, `rationale`). Vector search finds
|
|
22
|
-
it because the embedding of the query is geometrically
|
|
23
|
-
close to the embedding of the stored sentence.
|
|
24
|
-
|
|
25
|
-
This gap is identified as the critical lexical-only
|
|
26
|
-
ceiling in
|
|
27
|
-
[Opus 4.8 Second Brain Review](../issues/2026-06-23-opus-48-second-brain-review.md)
|
|
28
|
-
(gap #1). Hybrid search closes it by adding an optional
|
|
29
|
-
vector retrieval path alongside the existing lexical
|
|
30
|
-
path, fusing both ranked lists using Reciprocal Rank
|
|
31
|
-
Fusion (RRF) for a final result set that benefits from
|
|
32
|
-
both modes.
|
|
33
|
-
|
|
34
|
-
## Architecture
|
|
35
|
-
|
|
36
|
-
Two retrieval paths run in parallel. The vector path
|
|
37
|
-
runs in TypeScript, backed by a lightweight WASM crate
|
|
38
|
-
(`kbdb-default-embedding`) for the built-in TF-IDF
|
|
39
|
-
provider.
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
Query → ┌─ Lexical path (WASM) ─→ BM25F ranked list ─┐
|
|
43
|
-
│ ├─→ RRF Fusion → Final results
|
|
44
|
-
└─ Vector path (TS) ─→ Cosine ranked list ──┘
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
The existing WASM modules are untouched.
|
|
48
|
-
`query-parser.wasm` remains a pure-computation lexical
|
|
49
|
-
module. `kb-worker.wasm` continues to orchestrate
|
|
50
|
-
lexical retrieval via `fs-database.wasm` exactly as
|
|
51
|
-
today. No new exports or host imports are added to
|
|
52
|
-
any existing WASM module.
|
|
53
|
-
|
|
54
|
-
A new standalone WASM crate, `kbdb-default-embedding`,
|
|
55
|
-
provides the built-in TF-IDF embedding computation.
|
|
56
|
-
It depends only on `kbdb-shared` and requires no host
|
|
57
|
-
imports -- pure computation. It is loaded as an
|
|
58
|
-
independent WASM instance by the worker daemon
|
|
59
|
-
alongside the existing three modules.
|
|
60
|
-
|
|
61
|
-
The TypeScript daemon layer adds the vector path as an
|
|
62
|
-
independent retrieval step that runs after the lexical
|
|
63
|
-
path completes (or concurrently if the runtime allows).
|
|
64
|
-
The fused list is assembled in TypeScript before the
|
|
65
|
-
response is returned to the worker client.
|
|
66
|
-
|
|
67
|
-
## EmbeddingProvider Interface
|
|
68
|
-
|
|
69
|
-
### Why TypeScript, Not WASM
|
|
70
|
-
|
|
71
|
-
Three reasons embeddings are computed in TypeScript
|
|
72
|
-
rather than in WASM:
|
|
73
|
-
|
|
74
|
-
1. `wasm32-unknown-unknown` has no network I/O. Calling
|
|
75
|
-
an embedding API from WASM is impossible without a
|
|
76
|
-
host bridge that would defeat the purpose of running
|
|
77
|
-
in WASM.
|
|
78
|
-
2. Embedding models are too large for WASM. A typical
|
|
79
|
-
all-MiniLM-L6-v2 model is ~90 MB. Loading that inside
|
|
80
|
-
the WASM sandbox on every daemon startup is
|
|
81
|
-
impractical.
|
|
82
|
-
3. TypeScript allows pluggable backends. An interface-based
|
|
83
|
-
design lets the daemon swap providers (local ONNX,
|
|
84
|
-
remote API) without changing any WASM code.
|
|
85
|
-
|
|
86
|
-
### Interface Specification
|
|
87
|
-
|
|
88
|
-
```typescript
|
|
89
|
-
interface EmbeddingProvider {
|
|
90
|
-
embed(texts: string[]): Promise<Float32Array[]>;
|
|
91
|
-
dimensions(): number;
|
|
92
|
-
modelName(): string;
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
| Member | Description |
|
|
97
|
-
|--------------|----------------------------------------------|
|
|
98
|
-
| `embed` | Compute embeddings for a batch of input texts. Returns one `Float32Array` per input, each of length `dimensions()`. |
|
|
99
|
-
| `dimensions` | Fixed embedding dimensionality for this provider. All vectors stored under the same model must have this length. |
|
|
100
|
-
| `modelName` | Stable identifier for the model. Used for stale-detection when the configured model changes. |
|
|
101
|
-
|
|
102
|
-
### Implementations
|
|
103
|
-
|
|
104
|
-
Four concrete implementations are specified.
|
|
105
|
-
|
|
106
|
-
**`TfidfEmbeddingProvider`** -- lightweight built-in
|
|
107
|
-
provider backed by the `kbdb-default-embedding` WASM
|
|
108
|
-
crate. Computes feature-hashed sublinear TF vectors
|
|
109
|
-
(256 dimensions, L2-normalized) using kbdb-shared's
|
|
110
|
-
tokenization pipeline (NFC normalization, Unicode
|
|
111
|
-
segmentation, camelCase splitting, stop-word removal,
|
|
112
|
-
synonym expansion, Snowball stemming). This ensures
|
|
113
|
-
token consistency between the lexical index and the
|
|
114
|
-
TF-IDF vectors -- hybrid search fuses two views of
|
|
115
|
-
the same vocabulary. The default when no `[embedding]`
|
|
116
|
-
section is present in `worker.toml`. Enables hybrid
|
|
117
|
-
search out of the box with zero configuration and
|
|
118
|
-
zero external dependencies. Uses FNV-1a feature
|
|
119
|
-
hashing with the sign trick (Weinberger et al., 2009).
|
|
120
|
-
Model name: `tfidf-v1`.
|
|
121
|
-
|
|
122
|
-
**`NullEmbeddingProvider`** -- no-op, returns empty
|
|
123
|
-
arrays. Available for explicit use but no longer the
|
|
124
|
-
default. Returns 0 dimensions, which causes
|
|
125
|
-
`assertProvider` to reject hybrid/vector search
|
|
126
|
-
requests with a structured error.
|
|
127
|
-
|
|
128
|
-
**`OnnxEmbeddingProvider`** -- local offline inference
|
|
129
|
-
using ONNX Runtime. Default model: `all-MiniLM-L6-v2`
|
|
130
|
-
(384 dimensions). The model file is downloaded on first
|
|
131
|
-
use and cached in the OS user-data directory. Works on
|
|
132
|
-
both Deno and Node.js >= 20. No network dependency
|
|
133
|
-
after the initial model download. Suitable for
|
|
134
|
-
air-gapped or cost-sensitive deployments.
|
|
135
|
-
|
|
136
|
-
**`ApiEmbeddingProvider`** -- calls an external HTTP
|
|
137
|
-
embedding API. Compatible with OpenAI, Anthropic, and
|
|
138
|
-
any endpoint that accepts a `POST /embeddings` request
|
|
139
|
-
with a JSON body `{ "input": string[] }` and returns
|
|
140
|
-
`{ "data": [{ "embedding": number[] }] }`. Configured
|
|
141
|
-
via `worker.toml`. The API key is read from an
|
|
142
|
-
environment variable named by `api_key_env`.
|
|
143
|
-
|
|
144
|
-
## Vector Storage
|
|
145
|
-
|
|
146
|
-
### New Index Files
|
|
147
|
-
|
|
148
|
-
Two new files are added to `indexes/` alongside the
|
|
149
|
-
existing index files. They follow the same rebuild
|
|
150
|
-
semantics as `terms.idx`, `positions.idx`, and
|
|
151
|
-
`corpus.dat`: derived data, rebuildable from section
|
|
152
|
-
content, excluded from version control.
|
|
153
|
-
|
|
154
|
-
**`vectors.idx`** -- binary, fixed-width records.
|
|
155
|
-
|
|
156
|
-
Header layout:
|
|
157
|
-
|
|
158
|
-
| Bytes | Field | Type | Description |
|
|
159
|
-
|-------|----------------|--------|-------------------------------|
|
|
160
|
-
| 0-3 | magic | u8[4] | `b"KBVX"` -- format sentinel |
|
|
161
|
-
| 4 | version | u8 | Format version, currently `1` |
|
|
162
|
-
| 5-6 | dimensions | u16 | Vector length per record |
|
|
163
|
-
| 7-10 | record_count | u32 | Total number of records |
|
|
164
|
-
|
|
165
|
-
Each record immediately follows the header:
|
|
166
|
-
|
|
167
|
-
| Bytes | Field | Type | Description |
|
|
168
|
-
|----------------|----------------|----------------|-------------------|
|
|
169
|
-
| 0-15 | kbid_hash | u8[16] | First 16 bytes of the section kbid, used as a compact lookup key |
|
|
170
|
-
| 16 onwards | embedding | f32[dimensions]| Raw embedding vector |
|
|
171
|
-
|
|
172
|
-
Record size = `16 + dimensions * 4` bytes. All records
|
|
173
|
-
are the same size, enabling O(1) offset computation by
|
|
174
|
-
record index and binary search by kbid hash.
|
|
175
|
-
|
|
176
|
-
**`vectors-meta.toml`** -- human-readable metadata.
|
|
177
|
-
|
|
178
|
-
```toml
|
|
179
|
-
[vectors]
|
|
180
|
-
model_name = "all-MiniLM-L6-v2"
|
|
181
|
-
dimensions = 384
|
|
182
|
-
version = 1
|
|
183
|
-
last_built = "2026-06-23T10:00:00Z"
|
|
184
|
-
section_count = 2847
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
| Field | Description |
|
|
188
|
-
|-----------------|-------------------------------------------|
|
|
189
|
-
| `model_name` | Model name from `EmbeddingProvider.modelName()`. Used for stale detection. |
|
|
190
|
-
| `dimensions` | Must match the `dimensions` header in `vectors.idx`. |
|
|
191
|
-
| `version` | `vectors.idx` format version. |
|
|
192
|
-
| `last_built` | ISO 8601 timestamp of the last rebuild. |
|
|
193
|
-
| `section_count` | Number of sections embedded. |
|
|
194
|
-
|
|
195
|
-
### Updated Directory Layout
|
|
196
|
-
|
|
197
|
-
```
|
|
198
|
-
.kbdb/
|
|
199
|
-
└── indexes/
|
|
200
|
-
├── meta.toml
|
|
201
|
-
├── terms.idx
|
|
202
|
-
├── positions.idx
|
|
203
|
-
├── corpus.dat
|
|
204
|
-
├── references.idx
|
|
205
|
-
├── vectors.idx # new -- binary embedding store
|
|
206
|
-
└── vectors-meta.toml # new -- embedding metadata
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
## Embedding Lifecycle
|
|
210
|
-
|
|
211
|
-
### Learn Time
|
|
212
|
-
|
|
213
|
-
When a section is added via `kbdb learn`, the lexical
|
|
214
|
-
indexing pipeline runs first (unchanged). After lexical
|
|
215
|
-
indexing completes, the daemon asynchronously computes
|
|
216
|
-
an embedding for the section using the configured
|
|
217
|
-
`EmbeddingProvider`.
|
|
218
|
-
|
|
219
|
-
The embedding input is the concatenation of the
|
|
220
|
-
section's title and body content:
|
|
221
|
-
|
|
222
|
-
```
|
|
223
|
-
{title}\n{content}
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
The title is prepended because it carries strong
|
|
227
|
-
semantic weight (it receives a 2.0x BM25F field weight
|
|
228
|
-
for the same reason). Concatenating both fields gives
|
|
229
|
-
the embedding model the same semantic signal that BM25F
|
|
230
|
-
heading-weight gives to lexical scoring.
|
|
231
|
-
|
|
232
|
-
Sections with no body content (binary sections where
|
|
233
|
-
content is base64) embed only the title and
|
|
234
|
-
description. Sections with neither title nor body
|
|
235
|
-
content are not embedded.
|
|
236
|
-
|
|
237
|
-
Embedding is best-effort at learn time: if the provider
|
|
238
|
-
fails for a section, the section is still stored
|
|
239
|
-
lexically. A warning is emitted in the learn response
|
|
240
|
-
(`vector_embedding_failed: true`).
|
|
241
|
-
|
|
242
|
-
### Rebuild
|
|
243
|
-
|
|
244
|
-
`kbdb rebuild` regenerates all vector embeddings from
|
|
245
|
-
section content alongside the existing lexical index
|
|
246
|
-
rebuild. Sections are embedded in batches of up to 64
|
|
247
|
-
to respect provider rate limits. The `vectors.idx` and
|
|
248
|
-
`vectors-meta.toml` files are written atomically after
|
|
249
|
-
all embeddings are computed.
|
|
250
|
-
|
|
251
|
-
### Stale Detection
|
|
252
|
-
|
|
253
|
-
When the daemon starts, it reads `vectors-meta.toml`
|
|
254
|
-
and compares `model_name` to the currently configured
|
|
255
|
-
provider's `modelName()`. If they differ, the vector
|
|
256
|
-
index is marked stale. Stale vector indexes are not
|
|
257
|
-
used for retrieval. Search falls back to lexical-only
|
|
258
|
-
with a warning field in the response:
|
|
259
|
-
`vector_index_stale: true`.
|
|
260
|
-
|
|
261
|
-
The agent or operator must run `kbdb rebuild` to
|
|
262
|
-
regenerate vectors with the new model before hybrid or
|
|
263
|
-
vector search resumes.
|
|
264
|
-
|
|
265
|
-
## Score Fusion
|
|
266
|
-
|
|
267
|
-
### Reciprocal Rank Fusion (RRF)
|
|
268
|
-
|
|
269
|
-
RRF combines the lexical ranked list and the vector
|
|
270
|
-
ranked list into a single fused list without requiring
|
|
271
|
-
score normalization. BM25F scores and cosine similarity
|
|
272
|
-
scores live on different scales and cannot be directly
|
|
273
|
-
added -- RRF sidesteps this by working on ranks rather
|
|
274
|
-
than scores.
|
|
275
|
-
|
|
276
|
-
Formula:
|
|
277
|
-
|
|
278
|
-
```
|
|
279
|
-
score(d) = 1/(k + rank_lexical(d)) + 1/(k + rank_vector(d))
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
Where:
|
|
283
|
-
|
|
284
|
-
- `k = 60` (standard default, configurable via
|
|
285
|
-
`worker.toml` as `rrf_k`)
|
|
286
|
-
- `rank_lexical(d)` is the 1-based rank of document `d`
|
|
287
|
-
in the BM25F result list, or infinity if `d` does not
|
|
288
|
-
appear in the lexical results
|
|
289
|
-
- `rank_vector(d)` is the 1-based rank of document `d`
|
|
290
|
-
in the cosine similarity result list, or infinity if
|
|
291
|
-
`d` does not appear in the vector results
|
|
292
|
-
- A rank of infinity contributes 0 to the sum (since
|
|
293
|
-
`1/(k + ∞) = 0`)
|
|
294
|
-
|
|
295
|
-
Documents appearing in only one result set receive full
|
|
296
|
-
contribution from that set and zero from the other.
|
|
297
|
-
Documents appearing in both receive a higher combined
|
|
298
|
-
score than either path could produce alone, naturally
|
|
299
|
-
surfacing cross-path agreement.
|
|
300
|
-
|
|
301
|
-
The final result list is sorted by RRF score descending.
|
|
302
|
-
|
|
303
|
-
### Why RRF
|
|
304
|
-
|
|
305
|
-
- Parameter-light: `k = 60` is the well-established
|
|
306
|
-
default from the original RRF paper (Cormack et al.,
|
|
307
|
-
2009). It rarely needs tuning.
|
|
308
|
-
- No normalization: BM25F scores and cosine similarity
|
|
309
|
-
are not comparable in magnitude. RRF requires only
|
|
310
|
-
rank order, not the scores themselves.
|
|
311
|
-
- Robust: adding a low-quality second path cannot hurt
|
|
312
|
-
the top results badly because RRF dampens rank
|
|
313
|
-
contributions as `k` is approached.
|
|
314
|
-
|
|
315
|
-
## Search Mode Parameter
|
|
316
|
-
|
|
317
|
-
A new `search_mode` parameter controls which retrieval
|
|
318
|
-
paths are active for a given search request.
|
|
319
|
-
|
|
320
|
-
| Value | Retrieval behaviour | Embedding provider required |
|
|
321
|
-
|------------|---------------------------------------------|-----------------------------|
|
|
322
|
-
| `lexical` | Lexical path only. Current behaviour. Default. | No |
|
|
323
|
-
| `vector` | Vector path only. Cosine similarity ranking. | Yes |
|
|
324
|
-
| `hybrid` | Both paths, RRF fusion. | Yes |
|
|
325
|
-
|
|
326
|
-
The default is `lexical`. Existing integrations that do
|
|
327
|
-
not supply `search_mode` are unchanged. This is a
|
|
328
|
-
strict backward-compatibility guarantee.
|
|
329
|
-
|
|
330
|
-
When `[embedding]` is absent from `worker.toml`, the
|
|
331
|
-
`TfidfEmbeddingProvider` is active as the default.
|
|
332
|
-
Hybrid and vector search work out of the box using
|
|
333
|
-
feature-hashed TF vectors. If the user explicitly sets
|
|
334
|
-
`provider = "none"`, `NullEmbeddingProvider` is used
|
|
335
|
-
and hybrid/vector search returns a structured error:
|
|
336
|
-
|
|
337
|
-
```json
|
|
338
|
-
{
|
|
339
|
-
"error": "Vector search requires a configured embedding provider",
|
|
340
|
-
"code": "EMBEDDING_PROVIDER_NOT_CONFIGURED"
|
|
341
|
-
}
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
## CLI and MCP Surface
|
|
345
|
-
|
|
346
|
-
### CLI
|
|
347
|
-
|
|
348
|
-
The `search` subcommand gains an optional `--algo` flag:
|
|
349
|
-
|
|
350
|
-
```
|
|
351
|
-
kbdb search "database rationale"
|
|
352
|
-
kbdb search --algo hybrid "database rationale"
|
|
353
|
-
kbdb search --algo vector "database rationale"
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
Omitting `--algo` defaults to `lexical`. The flag is
|
|
357
|
-
not shown in the short help unless an embedding provider
|
|
358
|
-
is configured.
|
|
359
|
-
|
|
360
|
-
### MCP
|
|
361
|
-
|
|
362
|
-
The `search` MCP tool gains an optional `algo` parameter
|
|
363
|
-
(string enum: `"lexical" | "vector" | "hybrid"`). The
|
|
364
|
-
parameter is documented as optional in the tool schema
|
|
365
|
-
with a default of `"lexical"`.
|
|
366
|
-
|
|
367
|
-
### Response Envelope
|
|
368
|
-
|
|
369
|
-
`PagedSearchResult` gains two new fields:
|
|
370
|
-
|
|
371
|
-
| Field | Type | Description |
|
|
372
|
-
|--------------------|--------|---------------------------------|
|
|
373
|
-
| `search_mode` | string | The mode that was requested: `"lexical"`, `"vector"`, or `"hybrid"` |
|
|
374
|
-
| `retrieval_method` | string | The mode that was actually used: `"lexical"`, `"vector"`, or `"hybrid"`. Differs from `search_mode` when a fallback occurred (e.g. stale vector index forced `"lexical"`). |
|
|
375
|
-
|
|
376
|
-
Both fields are always present in the response,
|
|
377
|
-
including when `search_mode` was not supplied (in which
|
|
378
|
-
case both fields are `"lexical"`).
|
|
379
|
-
|
|
380
|
-
## Configuration
|
|
381
|
-
|
|
382
|
-
`worker.toml` gains an `[embedding]` section:
|
|
383
|
-
|
|
384
|
-
```toml
|
|
385
|
-
[embedding]
|
|
386
|
-
provider = "onnx" # "onnx" | "api" | "none"
|
|
387
|
-
model = "all-MiniLM-L6-v2"
|
|
388
|
-
dimensions = 384
|
|
389
|
-
rrf_k = 60 # RRF parameter k
|
|
390
|
-
|
|
391
|
-
# API provider only:
|
|
392
|
-
api_url = ""
|
|
393
|
-
api_key_env = "KBDB_API_KEY"
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
| Key | Default | Description |
|
|
397
|
-
|---------------|----------------------|------------------------------|
|
|
398
|
-
| `provider` | `"none"` | Which `EmbeddingProvider` implementation to use |
|
|
399
|
-
| `model` | `"all-MiniLM-L6-v2"` | Model name, passed to the provider |
|
|
400
|
-
| `dimensions` | `384` | Embedding vector length |
|
|
401
|
-
| `rrf_k` | `60` | RRF constant `k` |
|
|
402
|
-
| `api_url` | `""` | Base URL for API provider |
|
|
403
|
-
| `api_key_env` | `"KBDB_API_KEY"` | Environment variable holding the API key |
|
|
404
|
-
|
|
405
|
-
When `[embedding]` is absent from `worker.toml`,
|
|
406
|
-
`provider` defaults to `"none"` and the built-in
|
|
407
|
-
`TfidfEmbeddingProvider` is used (256-dimensional
|
|
408
|
-
feature-hashed TF vectors). To fully disable
|
|
409
|
-
embeddings, set `provider = "none"` explicitly.
|
|
410
|
-
|
|
411
|
-
## Fallback Behaviour
|
|
412
|
-
|
|
413
|
-
Three fallback conditions are defined. Each is surfaced
|
|
414
|
-
in the search response with a specific warning field
|
|
415
|
-
rather than silently degrading.
|
|
416
|
-
|
|
417
|
-
**No embedding provider configured.**
|
|
418
|
-
The built-in `TfidfEmbeddingProvider` is active by
|
|
419
|
-
default, so hybrid and vector search work without
|
|
420
|
-
configuration. If the user explicitly sets
|
|
421
|
-
`provider = "none"`, requesting `hybrid` or `vector`
|
|
422
|
-
returns a structured error with code
|
|
423
|
-
`EMBEDDING_PROVIDER_NOT_CONFIGURED`.
|
|
424
|
-
|
|
425
|
-
**Stale vector index (model mismatch).**
|
|
426
|
-
When `vectors-meta.toml` model name does not match the
|
|
427
|
-
configured provider's `modelName()`, the daemon marks
|
|
428
|
-
the vector index stale. Any search request that would
|
|
429
|
-
use the vector path falls back to `lexical`. The
|
|
430
|
-
response includes `retrieval_method: "lexical"` and
|
|
431
|
-
`vector_index_stale: true`. The correct remediation is
|
|
432
|
-
`kbdb rebuild`.
|
|
433
|
-
|
|
434
|
-
**Embedding failure during learn.**
|
|
435
|
-
If the embedding provider returns an error for a section
|
|
436
|
-
during learn, the section is stored lexically without an
|
|
437
|
-
embedding. The learn response includes
|
|
438
|
-
`vector_embedding_failed: true`. The section will
|
|
439
|
-
appear in lexical results but not in vector or hybrid
|
|
440
|
-
results until a rebuild is run.
|
|
441
|
-
|
|
442
|
-
## Provider Tradeoffs
|
|
443
|
-
|
|
444
|
-
| Aspect | TF-IDF (built-in) | ONNX (local) | API (cloud) |
|
|
445
|
-
|---------------|---------------------------|---------------------------|------------------------------|
|
|
446
|
-
| Latency | < 1 ms/embed | ~10 ms/embed | ~100 ms/embed |
|
|
447
|
-
| Offline | Yes | Yes | No |
|
|
448
|
-
| Cost | Free | Free | Per-token |
|
|
449
|
-
| Quality | Adequate (256 dimensions) | Good (384 dimensions) | Excellent (1536+ dimensions) |
|
|
450
|
-
| Setup | None (default) | Model download on first use | API key required |
|
|
451
|
-
| Dependencies | None | onnxruntime-node | None |
|
|
452
|
-
| Dual runtime | Deno + Node.js >= 20 | Deno + Node.js >= 20 | Deno + Node.js >= 20 |
|
|
453
|
-
|
|
454
|
-
All providers implement the same `EmbeddingProvider`
|
|
455
|
-
interface. The daemon interacts only with the interface.
|
|
456
|
-
|
|
457
|
-
## Design Constraints
|
|
458
|
-
|
|
459
|
-
| Constraint | Details |
|
|
460
|
-
|-----------------------|------------------------------------------------|
|
|
461
|
-
| WASM purity | `query-parser.wasm` remains pure lexical computation with no I/O. A new standalone crate, `kbdb-default-embedding`, is added for TF-IDF embedding but has no host imports. Existing WASM modules are unchanged. |
|
|
462
|
-
| Backward compatibility | Default `search_mode` is `lexical`. Callers that do not supply the parameter receive identical behaviour to today. |
|
|
463
|
-
| File-based storage | `vectors.idx` and `vectors-meta.toml` follow the same derived-and-rebuildable pattern as all other index files. |
|
|
464
|
-
| Dual runtime | `EmbeddingProvider` and all embedding logic must work on Deno and Node.js >= 20. No Node-specific or Deno-specific APIs in the implementation. |
|
|
465
|
-
| No new deps on existing WASM | The existing WASM modules (`fs-database.wasm`, `query-parser.wasm`, `kb-worker.wasm`) are not modified or recompiled. A new standalone module (`default-embedding.wasm`) is added, bringing the total to four WASM modules. |
|