@dikolab/kbdb 0.4.3 → 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.
Files changed (224) hide show
  1. package/CLA.md +68 -0
  2. package/LICENSE +676 -12
  3. package/LICENSING.md +58 -0
  4. package/README.md +50 -27
  5. package/README.md.bak +320 -0
  6. package/dist/README.md +320 -0
  7. package/dist/{chunk-BJXEVALU.mjs → chunk-QLJ33C74.mjs} +44 -44
  8. package/dist/chunk-QLJ33C74.mjs.map +7 -0
  9. package/dist/cli.cjs +550 -232
  10. package/dist/cli.cjs.map +4 -4
  11. package/dist/cli.d.ts +0 -8
  12. package/dist/cli.mjs +483 -154
  13. package/dist/cli.mjs.map +4 -4
  14. package/dist/kbdb-worker.cjs +36 -5
  15. package/dist/kbdb-worker.cjs.map +3 -3
  16. package/dist/mod.cjs +89 -30
  17. package/dist/mod.cjs.map +4 -4
  18. package/dist/mod.mjs +88 -30
  19. package/dist/mod.mjs.map +4 -4
  20. package/dist/src/cli.d.ts +0 -8
  21. package/dist/src/shared/cli/constants/defaults.constant.d.ts +1 -1
  22. package/dist/src/shared/cli/constants/recfile-command-field-map.constant.d.ts +8 -0
  23. package/dist/src/shared/cli/constants/recfile-field-maps.constant.d.ts +21 -0
  24. package/dist/src/shared/cli/functions/compute-directory-level.function.d.ts +14 -0
  25. package/dist/src/shared/cli/functions/dispatch-command.function.d.ts +2 -28
  26. package/dist/src/shared/cli/functions/format-command-output.function.d.ts +14 -0
  27. package/dist/src/shared/cli/functions/format-db-not-found.function.d.ts +20 -0
  28. package/dist/src/shared/cli/functions/format-output.function.d.ts +1 -0
  29. package/dist/src/shared/cli/functions/format-recfile-output.function.d.ts +11 -0
  30. package/dist/src/shared/cli/functions/render-recfile-value.function.d.ts +11 -0
  31. package/dist/src/shared/cli/functions/resolve-db-dir.function.d.ts +24 -0
  32. package/dist/src/shared/cli/functions/resolve-recfile-key.function.d.ts +9 -0
  33. package/dist/src/shared/cli/functions/run-db-path.function.d.ts +10 -0
  34. package/dist/src/shared/cli/functions/run-export.function.d.ts +8 -4
  35. package/dist/src/shared/cli/functions/run-import.function.d.ts +4 -0
  36. package/dist/src/shared/cli/functions/run-learn.function.d.ts +12 -0
  37. package/dist/src/shared/cli/index.d.ts +4 -1
  38. package/dist/src/shared/cli/typings/cli-options.interface.d.ts +9 -2
  39. package/dist/src/shared/cli/typings/db-resolution.interface.d.ts +9 -0
  40. package/dist/src/shared/cli/typings/recfile-field-map.type.d.ts +10 -0
  41. package/dist/src/shared/version/constants/version.constant.d.ts +1 -1
  42. package/dist/src/shared/wasm-codec/functions/decode-option-u8.function.d.ts +9 -0
  43. package/dist/src/shared/wasm-codec/functions/decode-section-records.function.d.ts +4 -0
  44. package/dist/src/shared/wasm-codec/functions/encode-add-section-params.function.d.ts +2 -45
  45. package/dist/src/shared/wasm-codec/functions/encode-update-section-params.function.d.ts +10 -1
  46. package/dist/src/shared/wasm-codec/index.d.ts +1 -1
  47. package/dist/src/shared/wasm-codec/typings/add-section-input.model.d.ts +6 -0
  48. package/dist/src/shared/wasm-codec/typings/section-record-trailing.model.d.ts +5 -0
  49. package/dist/src/shared/wasm-codec/typings/section-record.model.d.ts +5 -0
  50. package/dist/src/shared/worker-client/functions/build-deno-permissions.function.d.ts +11 -0
  51. package/dist/src/shared/worker-client/functions/read-crash-reason.function.d.ts +9 -0
  52. package/dist/src/shared/worker-client/functions/resolve-spawn-script.function.d.ts +6 -0
  53. package/dist/src/shared/worker-client/functions/spawn-daemon.function.d.ts +1 -1
  54. package/dist/src/shared/worker-client/typings/section.model.d.ts +5 -0
  55. package/dist/src/shared/worker-client/typings/worker-client-options.interface.d.ts +0 -6
  56. package/dist/src/shared/worker-daemon/functions/count-files.function.d.ts +12 -0
  57. package/dist/src/shared/worker-daemon/functions/handle-import.function.d.ts +2 -0
  58. package/dist/src/shared/worker-daemon/functions/handle-read-ops.function.d.ts +8 -0
  59. package/dist/src/shared/worker-daemon/functions/validate-daemon-path.function.d.ts +15 -0
  60. package/dist/src/worker.d.ts +0 -7
  61. package/dist/wasm/default-embedding/kbdb_default_embedding_bg.wasm +0 -0
  62. package/dist/wasm/default-embedding/package.json +1 -1
  63. package/dist/wasm/fs-database/kbdb_fs_database_bg.wasm +0 -0
  64. package/dist/wasm/fs-database/package.json +1 -1
  65. package/dist/wasm/fs-migration/kbdb_fs_migration_bg.wasm +0 -0
  66. package/dist/wasm/fs-migration/package.json +1 -1
  67. package/dist/wasm/kb-worker/kbdb_worker_bg.wasm +0 -0
  68. package/dist/wasm/kb-worker/package.json +1 -1
  69. package/dist/wasm/query-parser/kbdb_query_parser_bg.wasm +0 -0
  70. package/dist/wasm/query-parser/package.json +1 -1
  71. package/dist/worker.d.ts +0 -7
  72. package/dist/worker.mjs +37 -6
  73. package/dist/worker.mjs.map +3 -3
  74. package/package.json +13 -12
  75. package/dist/chunk-BJXEVALU.mjs.map +0 -7
  76. package/dist/src/shared/cli/functions/resolve-raw-db-path.function.d.ts +0 -10
  77. package/docs/details/README.md +0 -30
  78. package/docs/details/agent-tooling.md +0 -132
  79. package/docs/details/cli.md +0 -777
  80. package/docs/details/knowledge-base.md +0 -180
  81. package/docs/details/library-api.md +0 -495
  82. package/docs/details/mcp-server.md +0 -482
  83. package/docs/details/search-and-ranking.md +0 -575
  84. package/docs/details/storage.md +0 -531
  85. package/docs/goals/agents.md +0 -751
  86. package/docs/goals/architecture.svg +0 -198
  87. package/docs/goals/auto-capture.md +0 -378
  88. package/docs/goals/benchmarking.md +0 -296
  89. package/docs/goals/cli.md +0 -2654
  90. package/docs/goals/concurrency.md +0 -259
  91. package/docs/goals/content-composer.md +0 -609
  92. package/docs/goals/content-parser.md +0 -279
  93. package/docs/goals/database.md +0 -1679
  94. package/docs/goals/document.md +0 -368
  95. package/docs/goals/hybrid-search.md +0 -465
  96. package/docs/goals/mcp.md +0 -1541
  97. package/docs/goals/overview.md +0 -124
  98. package/docs/goals/query-parser.md +0 -373
  99. package/docs/goals/query-result.md +0 -860
  100. package/docs/goals/semantic-dedup.md +0 -233
  101. package/docs/goals/skills.md +0 -810
  102. package/docs/goals/sync.md +0 -217
  103. package/docs/goals/worker-client.md +0 -1005
  104. package/docs/goals/worker-daemon.md +0 -1547
  105. package/docs/modules/overview.md +0 -319
  106. package/docs/modules/rust/embedding/module.md +0 -91
  107. package/docs/modules/rust/fs-database/functions/document.md +0 -48
  108. package/docs/modules/rust/fs-database/functions/integrity.md +0 -79
  109. package/docs/modules/rust/fs-database/functions/io.md +0 -87
  110. package/docs/modules/rust/fs-database/functions/module.md +0 -22
  111. package/docs/modules/rust/fs-database/functions/query-exec.md +0 -44
  112. package/docs/modules/rust/fs-database/functions/section.md +0 -76
  113. package/docs/modules/rust/fs-database/indexes/btree.md +0 -35
  114. package/docs/modules/rust/fs-database/indexes/corpus.md +0 -61
  115. package/docs/modules/rust/fs-database/indexes/inverted.md +0 -60
  116. package/docs/modules/rust/fs-database/indexes/module.md +0 -64
  117. package/docs/modules/rust/fs-database/indexes/positional.md +0 -39
  118. package/docs/modules/rust/fs-database/indexes/vectors.md +0 -110
  119. package/docs/modules/rust/fs-database/module.md +0 -150
  120. package/docs/modules/rust/fs-database/types/catalog.md +0 -53
  121. package/docs/modules/rust/fs-database/types/module.md +0 -12
  122. package/docs/modules/rust/fs-database/types/section.md +0 -127
  123. package/docs/modules/rust/kb-worker/functions/compose.md +0 -57
  124. package/docs/modules/rust/kb-worker/functions/module.md +0 -20
  125. package/docs/modules/rust/kb-worker/functions/retrieve.md +0 -194
  126. package/docs/modules/rust/kb-worker/functions/search.md +0 -173
  127. package/docs/modules/rust/kb-worker/functions/write.md +0 -172
  128. package/docs/modules/rust/kb-worker/module.md +0 -171
  129. package/docs/modules/rust/kb-worker/types/cache.md +0 -90
  130. package/docs/modules/rust/kb-worker/types/context.md +0 -26
  131. package/docs/modules/rust/kb-worker/types/module.md +0 -16
  132. package/docs/modules/rust/kb-worker/types/status.md +0 -29
  133. package/docs/modules/rust/kb-worker/types/token.md +0 -50
  134. package/docs/modules/rust/overview.md +0 -531
  135. package/docs/modules/rust/query-parser/functions/extract.md +0 -56
  136. package/docs/modules/rust/query-parser/functions/module.md +0 -18
  137. package/docs/modules/rust/query-parser/functions/parse.md +0 -33
  138. package/docs/modules/rust/query-parser/functions/pipeline.md +0 -9
  139. package/docs/modules/rust/query-parser/functions/rank.md +0 -85
  140. package/docs/modules/rust/query-parser/module.md +0 -131
  141. package/docs/modules/rust/query-parser/types/cache.md +0 -39
  142. package/docs/modules/rust/query-parser/types/keyphrase.md +0 -71
  143. package/docs/modules/rust/query-parser/types/module.md +0 -18
  144. package/docs/modules/rust/query-parser/types/token.md +0 -46
  145. package/docs/modules/rust/shared/content-composer/functions.md +0 -114
  146. package/docs/modules/rust/shared/content-composer/module.md +0 -147
  147. package/docs/modules/rust/shared/content-composer/types.md +0 -93
  148. package/docs/modules/rust/shared/content-parser/functions.md +0 -72
  149. package/docs/modules/rust/shared/content-parser/module.md +0 -99
  150. package/docs/modules/rust/shared/content-parser/types.md +0 -71
  151. package/docs/modules/rust/shared/corpus/module.md +0 -24
  152. package/docs/modules/rust/shared/corpus/types.md +0 -141
  153. package/docs/modules/rust/shared/document/module.md +0 -25
  154. package/docs/modules/rust/shared/document/types.md +0 -154
  155. package/docs/modules/rust/shared/encode/module.md +0 -22
  156. package/docs/modules/rust/shared/encode/traits.md +0 -304
  157. package/docs/modules/rust/shared/error/module.md +0 -28
  158. package/docs/modules/rust/shared/error/types.md +0 -302
  159. package/docs/modules/rust/shared/kbid/module.md +0 -24
  160. package/docs/modules/rust/shared/kbid/types.md +0 -147
  161. package/docs/modules/rust/shared/memory/functions.md +0 -209
  162. package/docs/modules/rust/shared/memory/module.md +0 -24
  163. package/docs/modules/rust/shared/module.md +0 -196
  164. package/docs/modules/rust/shared/pipeline/functions.md +0 -141
  165. package/docs/modules/rust/shared/pipeline/module.md +0 -62
  166. package/docs/modules/rust/shared/pipeline/types.md +0 -43
  167. package/docs/modules/rust/shared/query/module.md +0 -27
  168. package/docs/modules/rust/shared/query/types.md +0 -236
  169. package/docs/modules/rust/shared/section/module.md +0 -25
  170. package/docs/modules/rust/shared/section/types.md +0 -294
  171. package/docs/modules/rust/shared/term/module.md +0 -25
  172. package/docs/modules/rust/shared/term/types.md +0 -139
  173. package/docs/modules/typescript/cli.md +0 -131
  174. package/docs/modules/typescript/kbdb-worker.md +0 -150
  175. package/docs/modules/typescript/overview.md +0 -400
  176. package/docs/modules/typescript/shared/auto-capture/module.md +0 -73
  177. package/docs/modules/typescript/shared/cli/constants.md +0 -23
  178. package/docs/modules/typescript/shared/cli/functions.md +0 -809
  179. package/docs/modules/typescript/shared/cli/module.md +0 -138
  180. package/docs/modules/typescript/shared/cli/typings.md +0 -197
  181. package/docs/modules/typescript/shared/embedding/functions.md +0 -200
  182. package/docs/modules/typescript/shared/embedding/module.md +0 -77
  183. package/docs/modules/typescript/shared/embedding/typings.md +0 -124
  184. package/docs/modules/typescript/shared/hash/functions.md +0 -20
  185. package/docs/modules/typescript/shared/hash/module.md +0 -21
  186. package/docs/modules/typescript/shared/log/constants.md +0 -82
  187. package/docs/modules/typescript/shared/log/functions.md +0 -131
  188. package/docs/modules/typescript/shared/log/module.md +0 -153
  189. package/docs/modules/typescript/shared/log/typings.md +0 -82
  190. package/docs/modules/typescript/shared/mcp/classes.md +0 -179
  191. package/docs/modules/typescript/shared/mcp/functions.md +0 -386
  192. package/docs/modules/typescript/shared/mcp/module.md +0 -160
  193. package/docs/modules/typescript/shared/mcp/typings.md +0 -529
  194. package/docs/modules/typescript/shared/module.md +0 -110
  195. package/docs/modules/typescript/shared/platform/functions.md +0 -42
  196. package/docs/modules/typescript/shared/platform/module.md +0 -25
  197. package/docs/modules/typescript/shared/runtime/functions.md +0 -26
  198. package/docs/modules/typescript/shared/runtime/module.md +0 -27
  199. package/docs/modules/typescript/shared/runtime/typings.md +0 -13
  200. package/docs/modules/typescript/shared/version/module.md +0 -27
  201. package/docs/modules/typescript/shared/wasm-codec/functions.md +0 -391
  202. package/docs/modules/typescript/shared/wasm-codec/module.md +0 -98
  203. package/docs/modules/typescript/shared/worker-client/classes.md +0 -264
  204. package/docs/modules/typescript/shared/worker-client/functions.md +0 -175
  205. package/docs/modules/typescript/shared/worker-client/module.md +0 -92
  206. package/docs/modules/typescript/shared/worker-client/typings.md +0 -511
  207. package/docs/modules/typescript/shared/worker-daemon/classes.md +0 -239
  208. package/docs/modules/typescript/shared/worker-daemon/constants.md +0 -37
  209. package/docs/modules/typescript/shared/worker-daemon/functions.md +0 -234
  210. package/docs/modules/typescript/shared/worker-daemon/module.md +0 -118
  211. package/docs/modules/typescript/shared/worker-daemon/typings.md +0 -132
  212. package/docs/overview.md +0 -191
  213. package/docs/release-notes/0.1.0.md +0 -189
  214. package/docs/release-notes/0.1.1.md +0 -46
  215. package/docs/release-notes/0.1.2.md +0 -38
  216. package/docs/release-notes/0.1.3.md +0 -42
  217. package/docs/release-notes/0.2.0.md +0 -147
  218. package/docs/release-notes/0.3.0.md +0 -89
  219. package/docs/release-notes/0.3.1.md +0 -121
  220. package/docs/release-notes/0.3.2.md +0 -64
  221. package/docs/release-notes/0.4.0.md +0 -80
  222. package/docs/release-notes/0.4.1.md +0 -149
  223. package/docs/release-notes/0.4.3.md +0 -60
  224. package/docs/release-notes/README.md +0 -13
@@ -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. |