knolo-core 0.2.3 → 3.1.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/README.md CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  # 🧠 KnoLo Core
3
2
 
4
3
  [![npm version](https://img.shields.io/npm/v/knolo-core.svg)](https://www.npmjs.com/package/knolo-core)
@@ -7,27 +6,34 @@
7
6
  [![GitHub license](https://img.shields.io/github/license/HiveForensics-AI/knolo-core.svg)](https://github.com/HiveForensics-AI/knolo-core/blob/main/LICENSE)
8
7
  [![Website](https://img.shields.io/badge/website-knolo.dev-2ea44f?logo=vercel)](https://www.knolo.dev/)
9
8
 
9
+ **KnoLo Core** is a **local-first knowledge retrieval engine** for LLM apps.
10
+ Build a portable `.knolo` pack and run deterministic lexical retrieval with optional semantic reranking using quantized embeddings.
10
11
 
11
-
12
- **KnoLo Core** is a **local-first knowledge base** for small LLMs.
13
- Package documents into a compact `.knolo` file and query them deterministically —
14
- **no embeddings, no vector DB, no cloud**. Ideal for **on‑device / offline** assistants.
12
+ - ✅ Local/offline-first runtime
13
+ - Deterministic lexical ranking and filtering
14
+ - Optional embedding-aware hybrid retrieval (no external vector DB required)
15
+ - Node.js + browser + React Native / Expo support
15
16
 
16
17
  ---
17
18
 
18
- ## ✨ Highlights (v0.2.0)
19
-
20
- * 🔎 **Stronger relevance:**
21
-
22
- * **Required phrase enforcement** (quoted & `requirePhrases`)
23
- * **Proximity bonus** using minimal term-span cover
24
- * **Optional heading boosts** when headings are present
25
- * 🌀 **Duplicate-free results:** **near-duplicate suppression** + **MMR diversity**
26
- * 🧮 **KNS tie‑breaker:** lightweight numeric signature to stabilize close ties
27
- * **Faster & leaner:** precomputed `avgBlockLen` in pack metadata
28
- * 📱 **Works in Expo/React Native:** safe TextEncoder/TextDecoder ponyfills
29
- * 📑 **Context Patches:** LLM‑friendly snippets for prompts
30
- * 🔒 **Local & private:** everything runs on device
19
+ ## ✨ What’s in v0.3.0
20
+
21
+ - **Deterministic lexical quality upgrades**
22
+ - required phrase enforcement (quoted + `requirePhrases`)
23
+ - corpus-aware BM25L with true IDF and block-length normalization
24
+ - proximity bonus via minimal term-span cover
25
+ - optional heading overlap boosts
26
+ - deterministic pseudo-relevance query expansion
27
+ - **Hybrid retrieval support**
28
+ - optional semantic rerank over top lexical candidates
29
+ - int8 L2-normalized embedding quantization with per-vector float16 scales
30
+ - weighted lexical/semantic score blending controls
31
+ - **Stability & diversity**
32
+ - near-duplicate suppression + MMR diversity
33
+ - KNS tie-break signal for stable close-score ordering
34
+ - **Portable packs**
35
+ - single `.knolo` artifact
36
+ - semantic payload embedded directly in pack when enabled
31
37
 
32
38
  ---
33
39
 
@@ -37,7 +43,7 @@ Package documents into a compact `.knolo` file and query them deterministically
37
43
  npm install knolo-core
38
44
  ```
39
45
 
40
- Dev from source:
46
+ Build from source:
41
47
 
42
48
  ```bash
43
49
  git clone https://github.com/HiveForensics-AI/knolo-core.git
@@ -48,249 +54,435 @@ npm run build
48
54
 
49
55
  ---
50
56
 
51
- ## 🚀 Usage
57
+ ## 🚀 Quickstart
52
58
 
53
- ### 1) Node.js (build mount query → patch)
59
+ ### 1) Build + mount + query
54
60
 
55
61
  ```ts
56
- import { buildPack, mountPack, query, makeContextPatch } from "knolo-core";
62
+ import { buildPack, mountPack, query, makeContextPatch } from 'knolo-core';
57
63
 
58
64
  const docs = [
59
- { id: "guide", heading: "React Native Bridge", text: "The bridge sends messages between JS and native. You can throttle events..." },
60
- { id: "throttle", heading: "Throttling", text: "Throttling reduces frequency of events to avoid flooding the bridge." },
61
- { id: "dvst", heading: "Debounce vs Throttle", text: "Debounce waits for silence; throttle guarantees a max rate." }
65
+ {
66
+ id: 'bridge-guide',
67
+ namespace: 'mobile',
68
+ heading: 'React Native Bridge',
69
+ text: 'The bridge sends messages between JS and native modules. Throttling limits event frequency.',
70
+ },
71
+ {
72
+ id: 'perf-notes',
73
+ namespace: 'mobile',
74
+ heading: 'Debounce vs Throttle',
75
+ text: 'Debounce waits for silence; throttle enforces a maximum trigger rate.',
76
+ },
62
77
  ];
63
78
 
64
- const bytes = await buildPack(docs); // build .knolo bytes
65
- const kb = await mountPack({ src: bytes }); // mount in-memory
66
- const hits = query(kb, '“react native” throttle', // quotes enforce phrase
67
- { topK: 5, requirePhrases: ["max rate"] });
79
+ const bytes = await buildPack(docs);
80
+ const kb = await mountPack({ src: bytes });
68
81
 
69
- console.log(hits);
70
- /*
71
- [
72
- { blockId: 2, score: 6.73, text: "...", source: "dvst" },
73
- ...
74
- ]
75
- */
82
+ const hits = query(kb, '"react native" throttle', {
83
+ topK: 5,
84
+ requirePhrases: ['maximum trigger rate'],
85
+ namespace: 'mobile',
86
+ });
76
87
 
77
- const patch = makeContextPatch(hits, { budget: "small" });
78
- console.log(patch);
88
+ const patch = makeContextPatch(hits, { budget: 'small' });
89
+ console.log(hits, patch);
79
90
  ```
80
91
 
81
- ### 2) CLI (build a `.knolo` file)
92
+ ### 2) CLI pack build
82
93
 
83
- Create `docs.json`:
94
+ `docs.json`:
84
95
 
85
96
  ```json
86
97
  [
87
- { "id": "guide", "heading": "Guide", "text": "Install deps...\n\n## Throttle\nLimit frequency of events." },
88
- { "id": "faq", "heading": "FAQ", "text": "What is throttling? It reduces event frequency." }
98
+ {
99
+ "id": "guide",
100
+ "heading": "Guide",
101
+ "text": "Install deps.\n\n## Throttle\nLimit event frequency."
102
+ },
103
+ {
104
+ "id": "faq",
105
+ "heading": "FAQ",
106
+ "text": "What is throttling? It reduces event frequency."
107
+ }
89
108
  ]
90
109
  ```
91
110
 
92
- Build:
93
-
94
111
  ```bash
95
- # writes knowledge.knolo
96
112
  npx knolo docs.json knowledge.knolo
113
+
114
+ # embed agents from a local directory (.json/.yml/.yaml)
115
+ npx knolo docs.json knowledge.knolo --agents ./examples/agents
97
116
  ```
98
117
 
99
- Then load it in your app:
118
+ Then query in app:
100
119
 
101
120
  ```ts
102
- import { mountPack, query } from "knolo-core";
103
- const kb = await mountPack({ src: "./knowledge.knolo" });
104
- const hits = query(kb, "throttle events", { topK: 3 });
121
+ import { mountPack, query } from 'knolo-core';
122
+
123
+ const kb = await mountPack({ src: './knowledge.knolo' });
124
+ const hits = query(kb, 'throttle events', { topK: 3 });
105
125
  ```
106
126
 
107
- ### 3) React / Expo
127
+ ---
128
+
129
+ ## 🔀 Hybrid retrieval with embeddings (recommended direction)
130
+
131
+ KnoLo’s core retrieval remains lexical-first and deterministic. Semantic signals are added as an **optional rerank stage** when lexical confidence is low (or forced).
132
+
133
+ ### Build a semantic-enabled pack
108
134
 
109
135
  ```ts
110
- import { Asset } from "expo-asset";
111
- import * as FileSystem from "expo-file-system";
112
- import { mountPack, query } from "knolo-core";
136
+ import { buildPack } from 'knolo-core';
137
+
138
+ // embeddings must align 1:1 with docs/block order
139
+ const embeddings: Float32Array[] = await embedDocumentsInOrder(docs);
140
+
141
+ const bytes = await buildPack(docs, {
142
+ semantic: {
143
+ enabled: true,
144
+ modelId: 'text-embedding-3-small',
145
+ embeddings,
146
+ quantization: { type: 'int8_l2norm', perVectorScale: true },
147
+ },
148
+ });
149
+ ```
113
150
 
114
- async function loadKB() {
115
- const asset = Asset.fromModule(require("./assets/knowledge.knolo"));
116
- await asset.downloadAsync();
151
+ ### Query with semantic rerank
117
152
 
118
- const base64 = await FileSystem.readAsStringAsync(asset.localUri!, { encoding: FileSystem.EncodingType.Base64 });
119
- const bytes = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
153
+ ```ts
154
+ import { mountPack, query, hasSemantic } from 'knolo-core';
155
+
156
+ const kb = await mountPack({ src: bytes });
157
+ const queryEmbedding = await embedQuery('react native bridge throttling');
158
+
159
+ const hits = query(kb, 'react native bridge throttling', {
160
+ topK: 8,
161
+ semantic: {
162
+ enabled: hasSemantic(kb),
163
+ mode: 'rerank',
164
+ topN: 50,
165
+ minLexConfidence: 0.35,
166
+ blend: { enabled: true, wLex: 0.75, wSem: 0.25 },
167
+ queryEmbedding,
168
+ force: false,
169
+ },
170
+ });
171
+ ```
120
172
 
121
- const kb = await mountPack({ src: bytes.buffer });
122
- return query(kb, `“react native” throttling`, { topK: 5 });
123
- }
173
+ ### Semantic helper utilities
174
+
175
+ ```ts
176
+ import {
177
+ quantizeEmbeddingInt8L2Norm,
178
+ encodeScaleF16,
179
+ decodeScaleF16,
180
+ } from 'knolo-core';
181
+
182
+ const { q, scale } = quantizeEmbeddingInt8L2Norm(queryEmbedding);
183
+ const packed = encodeScaleF16(scale);
184
+ const restored = decodeScaleF16(packed);
124
185
  ```
125
186
 
126
187
  ---
127
188
 
128
- ## 📑 API
189
+ ## 🧩 API
129
190
 
130
- ### `buildPack(docs) -> Promise<Uint8Array>`
131
-
132
- Builds a pack from an array of documents.
191
+ ### `buildPack(docs, opts?) => Promise<Uint8Array>`
133
192
 
134
193
  ```ts
135
194
  type BuildInputDoc = {
136
- id?: string; // optional doc id (exposed as hit.source)
137
- heading?: string; // optional heading (used for boosts)
138
- text: string; // raw markdown accepted (lightly stripped)
195
+ id?: string;
196
+ heading?: string;
197
+ namespace?: string;
198
+ text: string;
199
+ };
200
+
201
+ type BuildPackOptions = {
202
+ agents?: AgentRegistry | AgentDefinitionV1[];
203
+ semantic?: {
204
+ enabled: boolean;
205
+ modelId: string;
206
+ embeddings: Float32Array[];
207
+ quantization?: {
208
+ type: 'int8_l2norm';
209
+ perVectorScale?: true;
210
+ };
211
+ };
139
212
  };
140
213
  ```
141
214
 
142
- * Stores optional `heading` and `id` alongside each block.
143
- * Computes and persists `meta.stats.avgBlockLen` for faster queries.
215
+ ### Agents in pack metadata
144
216
 
145
- ### `mountPack({ src }) -> Promise<Pack>`
217
+ Agents are optional and embedded in `meta.agents` so a single `.knolo` artifact can ship retrieval behavior + prompt defaults on-prem. Agent registries are validated once at `mountPack()` time, so invalid embedded registries fail fast during mount.
146
218
 
147
- Loads a pack from a URL, `Uint8Array`, or `ArrayBuffer`.
219
+ Agent namespace binding is **strict**: when `resolveAgent()` composes retrieval options, `retrievalDefaults.namespace` is always enforced and caller-provided `query.namespace` is ignored.
220
+
221
+ ```ts
222
+ type AgentPromptTemplate = string[] | { format: 'markdown'; template: string };
223
+
224
+
225
+ type AgentRegistry = {
226
+ version: 1;
227
+ agents: AgentDefinitionV1[];
228
+ };
229
+
230
+ type PackMeta = {
231
+ version: number;
232
+ stats: { docs: number; blocks: number; terms: number; avgBlockLen?: number };
233
+ agents?: AgentRegistry;
234
+ };
235
+
236
+ type AgentDefinitionV1 = {
237
+ id: string;
238
+ version: 1;
239
+ name?: string;
240
+ description?: string;
241
+ systemPrompt: AgentPromptTemplate;
242
+ retrievalDefaults: {
243
+ namespace: string[]; // required
244
+ topK?: number;
245
+ queryExpansion?: QueryOptions['queryExpansion'];
246
+ semantic?: Omit<
247
+ NonNullable<QueryOptions['semantic']>,
248
+ 'queryEmbedding' | 'enabled' | 'force'
249
+ > & { enabled?: boolean };
250
+ minScore?: number;
251
+ requirePhrases?: string[];
252
+ source?: string[];
253
+ };
254
+ toolPolicy?: { mode: 'allow' | 'deny'; tools: string[] };
255
+ metadata?: Record<string, string | number | boolean | null>;
256
+ };
257
+ ```
258
+
259
+ ### `mountPack({ src }) => Promise<Pack>`
148
260
 
149
261
  ```ts
150
262
  type Pack = {
151
- meta: { version: number; stats: { docs: number; blocks: number; terms: number; avgBlockLen?: number } };
263
+ meta: {
264
+ version: number;
265
+ stats: {
266
+ docs: number;
267
+ blocks: number;
268
+ terms: number;
269
+ avgBlockLen?: number;
270
+ };
271
+ };
152
272
  lexicon: Map<string, number>;
153
273
  postings: Uint32Array;
154
274
  blocks: string[];
155
275
  headings?: (string | null)[];
156
276
  docIds?: (string | null)[];
277
+ namespaces?: (string | null)[];
278
+ blockTokenLens?: number[];
279
+ semantic?: {
280
+ version: 1;
281
+ modelId: string;
282
+ dims: number;
283
+ encoding: 'int8_l2norm';
284
+ perVectorScale: boolean;
285
+ vecs: Int8Array;
286
+ scales?: Uint16Array;
287
+ };
157
288
  };
158
289
  ```
159
290
 
160
- > **Compatibility:** v0.2.0 reads both v1 packs (string-only blocks) and v2 packs (objects with `text/heading/docId`).
161
-
162
- ### `query(pack, q, opts) -> Hit[]`
163
-
164
- Deterministic lexical search with phrase enforcement, proximity, and de‑duplication.
291
+ ### `query(pack, queryText, opts?) => Hit[]`
165
292
 
166
293
  ```ts
167
294
  type QueryOptions = {
168
- topK?: number; // default 10
169
- requirePhrases?: string[]; // additional phrases to require (unquoted)
295
+ topK?: number;
296
+ minScore?: number;
297
+ requirePhrases?: string[];
298
+ namespace?: string | string[];
299
+ source?: string | string[];
300
+ queryExpansion?: {
301
+ enabled?: boolean;
302
+ docs?: number;
303
+ terms?: number;
304
+ weight?: number;
305
+ minTermLength?: number;
306
+ };
307
+ semantic?: {
308
+ enabled?: boolean;
309
+ mode?: 'rerank';
310
+ topN?: number;
311
+ minLexConfidence?: number;
312
+ blend?: {
313
+ enabled?: boolean;
314
+ wLex?: number;
315
+ wSem?: number;
316
+ };
317
+ queryEmbedding?: Float32Array;
318
+ force?: boolean;
319
+ };
170
320
  };
171
321
 
172
322
  type Hit = {
173
323
  blockId: number;
174
324
  score: number;
175
325
  text: string;
176
- source?: string; // docId if provided at build time
326
+ source?: string;
327
+ namespace?: string;
177
328
  };
178
329
  ```
179
330
 
180
- **What happens under the hood (v0.2.0):**
331
+ ### Agent runtime helpers
181
332
 
182
- * Tokenize + **enforce all phrases** (quoted in `q` and `requirePhrases`)
183
- * Candidate generation via inverted index
184
- * **Proximity bonus** using minimal window covering all query terms
185
- * Optional **heading overlap boost** (when headings are present)
186
- * Tiny **KNS** numeric-signature tie‑breaker (\~±2% influence)
187
- * **Near-duplicate suppression** (5‑gram Jaccard) + **MMR** diversity for top‑K
333
+ - `listAgents(pack) => string[]`
334
+ - `getAgent(pack, agentId) => AgentDefinitionV1 | undefined`
335
+ - `resolveAgent(pack, { agentId, query?, patch? }) => { agent, systemPrompt, retrievalOptions }`
336
+ - `buildSystemPrompt(agent, patch?) => string`
337
+ - `isToolAllowed(agent, toolId) => boolean` (defaults to allow-all when no `toolPolicy`)
338
+ - `assertToolAllowed(agent, toolId) => void` (throws deterministic error when blocked)
188
339
 
189
- ### `makeContextPatch(hits, { budget }) -> ContextPatch`
190
-
191
- Create structured snippets for LLM prompts.
340
+ ### Build a pack with agents and resolve at runtime
192
341
 
193
342
  ```ts
194
- type ContextPatch = {
195
- background: string[];
196
- snippets: Array<{ text: string; source?: string }>;
197
- definitions: Array<{ term: string; def: string; evidence?: number[] }>;
198
- facts: Array<{ s: string; p: string; o: string; evidence?: number[] }>;
199
- };
343
+ import {
344
+ buildPack,
345
+ mountPack,
346
+ resolveAgent,
347
+ query,
348
+ isToolAllowed,
349
+ assertToolAllowed,
350
+ } from 'knolo-core';
351
+
352
+ const bytes = await buildPack(docs, {
353
+ agents: [
354
+ {
355
+ id: 'mobile.agent',
356
+ version: 1,
357
+ systemPrompt: {
358
+ format: 'markdown',
359
+ template: 'You are {{team}} support.',
360
+ },
361
+ retrievalDefaults: { namespace: ['mobile'], topK: 5 },
362
+ toolPolicy: { mode: 'allow', tools: ['search_docs'] },
363
+ },
364
+ ],
365
+ });
366
+
367
+ const pack = await mountPack({ src: bytes });
368
+ const resolved = resolveAgent(pack, {
369
+ agentId: 'mobile.agent',
370
+ patch: { team: 'mobile' },
371
+ query: { namespace: ['backend'], topK: 8 },
372
+ });
373
+
374
+ console.log(resolved.retrievalOptions.namespace); // ['mobile'] (strict binding)
375
+
376
+ if (isToolAllowed(resolved.agent, 'search_docs')) {
377
+ // invoke search_docs
378
+ }
379
+ assertToolAllowed(resolved.agent, 'search_docs');
380
+
381
+ const hits = query(pack, 'bridge throttle', resolved.retrievalOptions);
200
382
  ```
201
383
 
202
- Budgets: `"mini" | "small" | "full"`.
384
+ ### `makeContextPatch(hits, { budget }) => ContextPatch`
203
385
 
204
- ---
386
+ Budgets: `"mini" | "small" | "full"`
205
387
 
206
- ## 🧠 Relevance & De‑dupe Details
388
+ ---
207
389
 
208
- * **Phrases:**
209
- Quoted phrases in the query (e.g., `“react native”`) and any `requirePhrases` **must appear** in results. Candidates failing this are dropped before ranking.
390
+ ## 📚 More usage examples
210
391
 
211
- * **Proximity:**
212
- We compute the **minimum span** that covers all query terms and apply a gentle multiplier:
213
- `1 + 0.15 / (1 + span)` (bounded, stable).
392
+ ### Namespace + source filtering
214
393
 
215
- * **Heading Boost:**
216
- If you provide headings at build time, overlap with query terms boosts the score proportionally to the fraction of unique query terms present in the heading.
394
+ ```ts
395
+ const hits = query(kb, 'retry backoff', {
396
+ namespace: ['sdk', 'api'],
397
+ source: ['errors-guide', 'http-reference'],
398
+ topK: 6,
399
+ });
400
+ ```
217
401
 
218
- * **Duplicate Control:**
219
- We use **5‑gram Jaccard** to filter near‑duplicates and **MMR** (λ≈0.8) to promote diversity within the top‑K.
402
+ ### Phrase-only retrieval fallback behavior
220
403
 
221
- * **KNS Signature (optional spice):**
222
- A tiny numeric signature provides deterministic tie‑breaking without changing the overall retrieval behavior.
404
+ If your query has no free tokens but includes required phrases, KnoLo still forms candidates from phrase tokens and enforces phrase presence.
223
405
 
224
- ---
406
+ ```ts
407
+ const hits = query(kb, '"event loop"', { requirePhrases: ['single thread'] });
408
+ ```
225
409
 
226
- ## 🛠 Input Format & Pack Layout
410
+ ### Precision mode with minimum score
227
411
 
228
- **Input docs:**
229
- `{ id?: string, heading?: string, text: string }`
412
+ ```ts
413
+ const strictHits = query(kb, 'jwt refresh token rotation', {
414
+ topK: 5,
415
+ minScore: 2.5,
416
+ });
417
+ ```
230
418
 
231
- **Pack layout (binary):**
232
- `[metaLen:u32][meta JSON][lexLen:u32][lexicon JSON][postCount:u32][postings][blocksLen:u32][blocks JSON]`
419
+ ### Validate semantic query options early
233
420
 
234
- * `meta.stats.avgBlockLen` is persisted (v2).
235
- * `blocks JSON` may be:
421
+ ```ts
422
+ import { validateSemanticQueryOptions } from 'knolo-core';
423
+
424
+ validateSemanticQueryOptions({
425
+ enabled: true,
426
+ topN: 40,
427
+ minLexConfidence: 0.3,
428
+ blend: { enabled: true, wLex: 0.8, wSem: 0.2 },
429
+ queryEmbedding,
430
+ });
431
+ ```
236
432
 
237
- * **v1:** `string[]` (text only)
238
- * **v2:** `{ text, heading?, docId? }[]`
433
+ ---
239
434
 
240
- The runtime auto‑detects either format.
435
+ ## 🛠 Pack format and compatibility
241
436
 
242
- ---
437
+ Binary layout:
243
438
 
244
- ## 🔁 Migration (0.1.x → 0.2.0)
439
+ `[metaLen:u32][meta JSON][lexLen:u32][lexicon JSON][postCount:u32][postings][blocksLen:u32][blocks JSON][semLen:u32?][sem JSON?][semBlobLen:u32?][semBlob?]`
245
440
 
246
- * **No API breaks.** `buildPack`, `mountPack`, `query`, `makeContextPatch` unchanged.
247
- * Packs built with 0.1.x still load and query fine.
248
- * If you want heading boosts and `hit.source`, pass `heading` and `id` to `buildPack`.
249
- * React Native/Expo users no longer need polyfills—ponyfills are included.
441
+ - Supports legacy block payloads (`string[]`) and richer block objects (`{ text, heading, docId, namespace, len }`).
442
+ - Semantic section is optional and only present when built with `semantic.enabled = true`.
443
+ - `mountPack` auto-detects available sections.
250
444
 
251
445
  ---
252
446
 
253
- ## ⚡ Performance Tips
447
+ ## ⚡ Practical tuning guidance
254
448
 
255
- * Prefer multiple smaller blocks (≈512 tokens) over giant ones for better recall + proximity.
256
- * Provide `heading` for each block: cheap, high‑signal boost.
257
- * For large corpora, consider sharding packs by domain/topic to keep per‑pack size modest.
449
+ - Keep blocks reasonably small (~300–700 tokens) for better lexical recall and proximity precision.
450
+ - Include strong headings to increase cheap relevance gains.
451
+ - Use `namespace` to reduce candidate noise in multi-domain corpora.
452
+ - Start semantic blend near `wLex: 0.75 / wSem: 0.25`, then tune by offline eval.
453
+ - Keep embedding model consistent between build and query (`modelId` should match your query embedding source).
258
454
 
259
455
  ---
260
456
 
261
457
  ## ❓ FAQ
262
458
 
263
- **Q: Does this use embeddings?**
264
- No. Pure lexical retrieval (index, positions, BM25L, proximity, phrases).
459
+ **Does KnoLo require a vector database?**
460
+ No. Semantic vectors (when used) are stored in the `.knolo` pack and used for in-process reranking.
265
461
 
266
- **Q: Can I run this offline?**
267
- Yes. Everything is local.
462
+ **Is retrieval deterministic?**
463
+ Lexical retrieval and post-processing are deterministic. Semantic rerank is deterministic for fixed pack bytes and fixed embedding vectors.
268
464
 
269
- **Q: How do I prevent duplicates?**
270
- It’s built in (Jaccard + MMR). You can tune λ and similarity threshold in code if you fork.
465
+ **Can I run fully offline?**
466
+ Yes. Query-time needs no network. Build-time embeddings can also be offline if your embedding pipeline is local.
271
467
 
272
- **Q: Is RN/Expo supported?**
273
- Yes—TextEncoder/TextDecoder ponyfills are included.
468
+ **Is React Native / Expo supported?**
469
+ Yes. Runtime text encoder/decoder compatibility is included.
274
470
 
275
471
  ---
276
472
 
277
- ## 🗺️ Roadmap
473
+ ## 🗺️ Direction / roadmap
278
474
 
279
- * Multi-resolution packs (summaries + facts)
280
- * Overlay layers (user annotations)
281
- * WASM core for big-browser indexing
282
- * Delta updates / append-only patch packs
475
+ - stronger hybrid retrieval evaluation tooling
476
+ - richer pack introspection and diagnostics
477
+ - incremental pack update workflows
478
+ - continued local-first performance optimization
283
479
 
284
480
  ---
285
481
 
286
482
  ## 🌐 Website
287
483
 
288
- For docs, news, and examples visit **[knolo.dev](https://www.knolo.dev/)**
289
-
290
- ---
291
-
484
+ For docs, release updates, and examples: **[knolo.dev](https://www.knolo.dev/)**
292
485
 
293
486
  ## 📄 License
294
487
 
295
488
  Apache-2.0 — see [LICENSE](./LICENSE).
296
-