pikelet-wasm 0.7.0 → 0.8.1

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 (34) hide show
  1. package/CHANGELOG.md +340 -4
  2. package/NOTICE +5 -0
  3. package/README.md +226 -475
  4. package/complete/builder.d.ts +4 -4
  5. package/complete/builder.mjs +26 -9
  6. package/complete/encoder-kernels/encoder.node.wasm +0 -0
  7. package/complete/encoder-kernels/encoder.wasm +0 -0
  8. package/complete/fusion.mjs +72 -0
  9. package/complete/index.d.ts +34 -7
  10. package/complete/index.mjs +258 -129
  11. package/complete/inline-transformer.mjs +119 -10
  12. package/complete/lexical.mjs +11 -11
  13. package/complete/retrieval-abstention.mjs +136 -31
  14. package/complete/sources.mjs +22 -1
  15. package/complete/wordpiece.mjs +22 -0
  16. package/dist/engine.js +1 -1
  17. package/dist/engine.scalar.js +1 -1
  18. package/dist/engine.scalar.wasm +0 -0
  19. package/dist/engine.wasm +0 -0
  20. package/package.json +47 -82
  21. package/{pancake-artifact-common.js → src/artifact/pikelet-artifact-common.js} +42 -42
  22. package/{pancake-artifact-range.js → src/artifact/pikelet-artifact-range.js} +43 -36
  23. package/{pancake-artifact-sketch.js → src/artifact/pikelet-artifact-sketch.js} +105 -68
  24. package/{pancake-artifact.d.ts → src/artifact/pikelet-artifact.d.ts} +11 -11
  25. package/{pancake-artifact.js → src/artifact/pikelet-artifact.js} +10 -10
  26. package/{pancake-core.js → src/core/pikelet-core.js} +141 -140
  27. package/{pancake-errors.js → src/errors/pikelet-errors.js} +9 -8
  28. package/{pancake.d.ts → src/pikelet.d.ts} +55 -55
  29. package/{pancake.js → src/pikelet.js} +47 -47
  30. package/{pancake.node.mjs → src/pikelet.node.mjs} +49 -49
  31. package/{pancake.web.d.ts → src/pikelet.web.d.ts} +13 -13
  32. package/{pancake.web.mjs → src/pikelet.web.mjs} +24 -24
  33. package/{pancake.workerd.mjs → src/pikelet.workerd.mjs} +22 -22
  34. /package/{pancake-loader.js → src/loader/pikelet-loader.js} +0 -0
package/README.md CHANGED
@@ -1,589 +1,340 @@
1
1
  # Pikelet
2
2
 
3
- Pikelet is a search engine you host like a file.
3
+ **Knowledge that ships as a file.**
4
4
 
5
- > Renamed from **Pancake** (2026-09). Same project, same wire format —
6
- > existing `.pancake` artifacts and their identities remain valid, and the
7
- > old package names are deprecated pointers to `pikelet-wasm` and
8
- > `pikelet`.
5
+ Pikelet compiles a corpus into one self-contained, queryable artifact.
9
6
 
10
- At the bottom there's a small WebAssembly HNSW vector engine that runs
11
- anywhere JavaScript does — Node, browsers, Cloudflare Workers — with no
12
- native dependencies. On top of it sit immutable search artifact formats:
13
- files that package an index, corpus records, query interpretation,
14
- calibration, and evaluation data, and that can be served from any storage
15
- that answers byte-range requests.
7
+ A model can interrogate a 456,153-record knowledge base whose backend is a static file.
16
8
 
17
- Where this has been heading is the **complete search artifact**: one
18
- `.pikelet` file that answers natural-language queries with hydrated,
19
- confidence-scored results, using nothing but the file, range reads, and a
20
- reader.
9
+ A `.pikelet` can carry the source text, semantic index, keyword index, query encoder, integrity commitments, retrieval calibration, and evaluation fixtures needed to interrogate that corpus. Put the file on disk, S3, R2, a CDN, or any static HTTP host. A reader can mount it locally or over HTTP Range and search it without a vector database, embedding API, or retrieval server.
21
10
 
22
- ```text
23
- query text
24
- -> artifact-carried query interpreter
25
- -> vector search over compressed sketch/index data
26
- + BM25 over the embedded lexical index, rank-fused
27
- -> lazy range reads for records
28
- -> calibrated hydrated results
29
- ```
30
-
31
- The `pikelet-wasm` package carries both the vector engine and the artifact
32
- runtime.
33
-
34
- ## Knowledge Packs
35
-
36
- The same file doubles as a **knowledge pack** for LLMs. A `.pikelet` is a
37
- portable, immutable corpus that contains its own query encoder, semantic
38
- and lexical indexes, calibrated abstention, provenance, and integrity
39
- metadata. Publish it as a static file; an MCP client attaches and queries
40
- it directly over HTTP range requests — no index to rebuild, no vector
41
- database to run, no embedding service to call:
11
+ The `pikelet` CLI requires Node 20+; the `pikelet-wasm` library runs on Node 18+ (CI tests 18, 20, 22), browsers, and Cloudflare Workers.
42
12
 
43
13
  ```bash
44
- npx pikelet mcp install --client claude-code \
45
- --pack https://github.com/mcn92/pancake/releases/download/artifact-wiki-inline-v4/pancake-wiki-inline.pancake#1b180adf4c6cebb2dcd5615256df6a25dac5fda8738dbbc11d60af86046f97f3
14
+ npx pikelet compile --source ./docs --out docs.pikelet
46
15
  ```
47
16
 
48
- Then ask questions. That line pins 456,153 passages of Simple English
49
- Wikipedia by content hash: the mount transfers a ~52 MiB resident slice
50
- of the 649 MiB file, each question costs ~127 range reads, results carry
51
- their provenance (pack identity, title, section, source), and a pack that
52
- cannot answer says so instead of guessing. Details in the
53
- pikelet README ("Attaching packs to an LLM") and `packs/`.
54
-
55
- ## What's in This Repo
56
-
57
- Three layers, bottom to top:
58
-
59
- 1. **WASM vector engine**
60
- - In-memory HNSW index with float32 and row-wise affine uint8 backends.
61
- - JavaScript API: `Pikelet.create()`, `add()`, `search()`, `export()`,
62
- `restore()`.
63
- - Source in `src/`, package entrypoints at `pancake.js`,
64
- `pancake.node.mjs`, `pancake.web.mjs`.
65
-
66
- 2. **Search artifacts**
67
- - Immutable, integrity-checked files built from indexes and corpus data,
68
- range-readable from local files, R2, S3, CDNs — anything that serves
69
- byte ranges.
70
- - One product format, one index layer beneath it:
71
- - `.pikelet`: the complete one-file search artifact — the product.
72
- - `.pancake-sketch`: its index segment (resident sketch plus lazy
73
- rerank rows), also usable standalone; format 2 (the builder's
74
- default) interleaves per-row digests with the rows, so every row a
75
- query fetches is verified on that read.
76
- - Two adjacent formats that are not search artifacts in the contract
77
- sense:
78
- - `.pnck`: the engine's snapshot envelope (`export()`/`restore()`
79
- serialization), consumed by the compilers and by layer-1 users.
80
- - `.pancake-range`: **deprecated** range-readable index profile. The
81
- sketch geometry replaced it (5x faster over real networks at a
82
- third of the size); readers stay supported for existing artifacts,
83
- but no new ones should be built.
84
- - Specs: [spec/SEARCH_ARTIFACT_CONTRACT.md](spec/SEARCH_ARTIFACT_CONTRACT.md),
85
- [spec/SKETCH_PROFILE.md](spec/SKETCH_PROFILE.md), and
86
- [spec/COMPLETE_PROFILE.md](spec/COMPLETE_PROFILE.md).
87
-
88
- 3. **Products and compilers**
89
- - `pikelet/`: the fast path. One command compiles a docs
90
- folder or a live site into a complete `.pikelet` file:
91
- ```bash
92
- npx pikelet compile --source ./docs --out search.pikelet
93
- ```
94
- or scaffolds a deployable search app:
95
- ```bash
96
- npx pikelet create --name my-docs-search --source ./docs --no-deploy --yes
97
- ```
98
- - `examples/05-one-file-search/`: the flagship path — the complete
99
- `.pikelet` container, its compilers, and acceptance tests.
100
- - `examples/04-static-wiki-pack/`: wiki-scale corpus preparation and the
101
- sketch artifact pipeline.
102
- - `examples/03-edge-docs-search/`: a Worker search app with a bundled
103
- snapshot, distilled query encoder, and calibrated abstention.
104
-
105
- ## Why Pikelet Exists
106
-
107
- Most vector search libraries assume a server process, native binaries, or a
108
- database service. Pikelet is built for a different deployment shape:
109
-
110
- - static files instead of always-on index servers;
111
- - byte-range reads instead of whole-index downloads;
112
- - readers for browsers, Workers, and Node;
113
- - explicit artifact identity and digest verification;
114
- - query interpretation and evaluation data carried inside the artifact;
115
- - compact corpus-side vector representations that keep memory and transfer
116
- costs sane.
117
-
118
- The result is search you can host like an asset instead of operating like a
119
- database.
120
-
121
- ## The Flagship: One-File Search
122
-
123
- The complete profile (`.pikelet`) packs the five components of a search
124
- application into one content-addressed file:
125
-
126
17
  ```text
127
- index embedded .pancake-sketch artifact
128
- corpus JSON records behind an offsets table
129
- encoder query interpreter declaration or inline model data
130
- calibration abstention / match-quality model
131
- evaluation golden queries and expected behavior
132
- lexical BM25 inverted index for hybrid retrieval (optional; readers
133
- that predate it serve vector-only from the same file)
18
+ Ingested 3 docs -> 3 chunks
19
+ Embedded 3/3 chunks with inline transformer
20
+ Built complete .pikelet artifact with 24.5 MB
21
+ Compiled docs.pikelet
22
+ 24.5 MB, 3 records, identity 8d731a...
134
23
  ```
135
24
 
136
- The reader verifies the manifest and eager segments, opens the sketch index
137
- against a range source, and hydrates only the records the final results
138
- need.
139
-
140
- Query interpretation comes in three kinds. Each picks two of
141
- **self-contained**, **small**, and **teacher-quality**, and gives up the
142
- third — that trade-off, not history, is why all three exist:
143
-
144
- - **kind 3: `inline-transformer-v1`** — self-contained + teacher quality.
145
- The artifact carries the WordPiece vocab and quantized MiniLM teacher
146
- weights as verified data; the reader ships the execution kernels. Costs
147
- ~25 MB of encoder no matter how small the corpus. This is the default:
148
- `pikelet compile` builds kind 3.
149
- - **kind 1: `student-inline-v1`** — self-contained + small. A
150
- corpus-distilled student encoder (~1 MiB), pure-JS execution. The only
151
- way a small docs corpus compiles to a file measured in single-digit MB;
152
- quality is bounded by the distillation.
153
- - **kind 2: `external-transformers-v1`** — small + teacher quality, but
154
- not self-contained: the artifact pins a host-supplied encoder (model id,
155
- pooling, normalization, max tokens) and verifies it against embedded
156
- test vectors before serving. The escape hatch for encoders too big to
157
- inline and hosts that already run one.
158
-
159
- Kind 3 is the flagship: the file carries the corpus, index, tokenizer,
160
- encoder weights, calibration, and evaluation data — the reader supplies
161
- code, the artifact supplies everything else. Ask it about volcanoes and no
162
- service, model host, or network dependency is involved beyond range reads
163
- of the file itself.
25
+ First run fetches the ~25 MiB query encoder from a GitHub release and caches it; every `.pikelet` file is at least that size regardless of corpus, because the encoder ships inside it — a 5-file folder and a 500-file folder both start around 25 MiB.
26
+
27
+ Then query it from an LLM — Claude Code, Claude Desktop, or any MCP client:
164
28
 
165
29
  ```bash
166
- cd examples/05-one-file-search
167
- node compile-wiki.mjs --inline-encoder
168
- node compile.mjs --inspect pancake-wiki-inline.pancake
169
- node test-inline.mjs
30
+ npx pikelet mcp install --client claude-code --pack ./docs.pikelet
31
+ # or --client claude-desktop; any other MCP client can run
32
+ # `npx pikelet mcp --pack ./docs.pikelet` directly, no install step
170
33
  ```
171
34
 
172
- Where the inline wiki artifact currently stands:
173
-
174
35
  ```text
175
- 456,153 records
176
- ~649 MiB complete artifact (680,029,254 bytes)
177
- manifest identity: 1b180adf4c6cebb2dcd5615256df6a25dac5fda8738dbbc11d60af86046f97f3
178
- recall@10: 95.2% at the recommended rerank (97.0% at C=600), against exact
179
- fp32 brute-force ground truth on the 200-query pre-registered eval —
180
- 98.5% on hand-written natural-language questions, 94.8% on single-title
181
- lookups (augmented retrieval: hybrid candidates, distance order)
182
- serves over HTTP range reads: opens on ~52 MiB of the file, ~127 range
183
- requests per query (cluster-ordered row layout + manifest fetch hints)
184
- natural-language query with no host encoder option, ~110 ms/query end to
185
- end locally (the resident scan auto-stages the engine's SIMD kernel at
186
- this scale; ~540 ms on the pure-JS scan it falls back to)
36
+ Wrote MCP server "knowledge-packs" to ./.mcp.json
37
+ Claude Code picks it up on the next session in this project.
187
38
  ```
188
39
 
189
- Release asset (v4 — recovers the pack's canonical build: k-means
190
- cluster-ordered rows and the 192-dim 2:1-pooled sketch, where v1–v3
191
- embedded a 96-dim 4:1 sketch in unpermuted order and measured 82.4%; adds
192
- the hybrid BM25 lexical segment and a format-2 sketch with per-row read
193
- verification; earlier versions stay published for older checkouts):
194
- `https://github.com/mcn92/pancake/releases/download/artifact-wiki-inline-v4/pancake-wiki-inline.pancake`
40
+ Claude now has a `search` tool over your docs. Or query it directly from code:
195
41
 
196
- `node test-inline.mjs` downloads that file automatically when it is missing
197
- locally, verifies the manifest identity, and then runs the acceptance
198
- checks.
199
-
200
- See [examples/05-one-file-search/README.md](examples/05-one-file-search/README.md)
201
- for the full one-file walkthrough.
42
+ ```js
43
+ import { openPikeletFile } from 'pikelet-wasm/complete';
44
+ const pack = await openPikeletFile('docs.pikelet');
45
+ const out = await pack.query('how do workers restore snapshots', { k: 5 });
46
+ console.log(out.matchQuality, out.results[0]?.title);
47
+ // 'strong' 'Snapshot restore'
48
+ ```
202
49
 
203
- ## The Design Choice Everything Hangs On
50
+ That's the whole loop. `compile` also takes a live URL (`--source https://docs.example.com`) instead of a directory. If you want a deployed search app — a Worker + UI, not a file — use `npx pikelet create` instead; see [`packages/pikelet/README.md`](packages/pikelet/README.md) for the full CLI reference and the tradeoffs between the two. `compile` never needs the scaffold path's `@xenova/transformers` dependency; skip its ~140 MB install with `npm install -g pikelet --omit=optional` if you only need `compile`/`mcp`.
204
51
 
205
- The engine's quantized backend stores corpus vectors as row-wise affine
206
- uint8:
52
+ Under the hood, the file is one container for everything a reader needs:
207
53
 
208
54
  ```text
209
- value ~= offset[row] + scale[row] * byte
55
+ documents
56
+ │
57
+ ▼
58
+ ┌──────────────────┐
59
+ │ docs.pikelet │
60
+ │ │
61
+ │ corpus │
62
+ │ semantic index │
63
+ │ lexical index │
64
+ │ query encoder │
65
+ │ integrity │
66
+ │ calibration │
67
+ │ evaluation │
68
+ └──────────────────┘
69
+ │
70
+ ├── local file
71
+ └── static HTTP / object storage
72
+ │
73
+ ▼
74
+ browser / Node / edge / agent
210
75
  ```
211
76
 
212
- That one constraint propagates through the whole system:
77
+ Search is the interface. **The file is the knowledge deployment unit.**
213
78
 
214
- - HNSW search keeps the query as float32 and scores it against compressed
215
- corpus rows.
216
- - Distance kernels fuse dequantization with dot/L2 accumulation.
217
- - SIMD paths load bytes, widen lanes, apply scale/offset, and accumulate.
218
- - Graph maintenance uses symmetric node-to-node distances derived from the
219
- compressed rows and row statistics.
220
- - Snapshots serialize compact scale/offset/qdata arrays.
221
- - Sketch artifacts scan compressed resident rows and fetch only rerank
222
- candidates.
223
- - Range-backed readers keep the large corpus side compressed while queries
224
- stay precise and transient.
79
+ ---
225
80
 
226
- This is why the engine work and the artifact work belong in one project:
227
- the same corpus-side representation serves in-memory search, serialized
228
- snapshots, resident sketches, range reads, and reranking.
81
+ ## The shortest demonstration
229
82
 
230
- ## Install
83
+ A real Pikelet pack contains **456,153 Simple English Wikipedia records** in a **648.5 MiB** artifact.
231
84
 
232
- From npm:
85
+ Try the live browser demo:
233
86
 
234
- ```bash
235
- npm install pancake-wasm
87
+ ```text
88
+ https://pikelet-wiki-playground.pages.dev/
236
89
  ```
237
90
 
238
- From a checkout:
91
+ The page is static HTML/JS on Cloudflare Pages. It mounts the pack from R2:
239
92
 
240
- ```bash
241
- git clone https://github.com/mcn92/pancake.git
242
- cd pancake
243
- npm install
93
+ ```text
94
+ https://pub-6da2384a3bca4a44b2b2fa29a94cc811.r2.dev/wikipedia.pikelet
244
95
  ```
245
96
 
246
- The checkout includes prebuilt WASM in `dist/`, so you don't need to
247
- rebuild the engine unless you edit `src/`.
97
+ There is no `/search`, `/embed`, `/query`, or `/api` backend behind the demo.
98
+ The browser opens the `.pikelet` file over HTTP Range, verifies the artifact,
99
+ loads the embedded query encoder, and shows the byte ranges it reads.
248
100
 
249
- ## Runtime Entry Points
101
+ That file was served by a deliberately dumb HTTP server whose only relevant capability was:
250
102
 
251
- Published package:
252
-
253
- ```js
254
- // Node.js CJS
255
- const Pikelet = require('pikelet-wasm');
256
-
257
- // Node.js ESM
258
- import Pikelet from 'pikelet-wasm';
259
-
260
- // Explicit Node/browser entries
261
- import Pikelet from 'pikelet-wasm/node';
262
- import Pikelet from 'pikelet-wasm/web';
263
-
264
- // Search Artifact layer (sketch readers/builders; the deprecated
265
- // .pancake-range reader stays exported for existing artifacts)
266
- import { PancakeSketchArtifact, buildSketchArtifact } from 'pikelet-wasm/artifact';
267
-
268
- // Complete one-file profile: reader (any runtime) and builder (Node)
269
- import { openPikeletFile } from 'pikelet-wasm/complete';
270
- import { assemblePancakeFile } from 'pikelet-wasm/complete/builder';
271
- ```
272
-
273
- Repository checkout:
274
-
275
- ```js
276
- const Pikelet = require('./pancake.js');
277
- import Pikelet from './pancake.node.mjs';
278
- import Pikelet from './pancake.web.mjs';
103
+ ```text
104
+ fs.createReadStream + HTTP Range
279
105
  ```
280
106
 
281
- The browser entry expects the runtime or bundler to resolve
282
- `dist/engine.wasm`.
107
+ There was no vector database, no search service, no embedding service, no application server that understood the pack, no index process on the server.
283
108
 
284
- ## Engine Quick Start
109
+ The pack was mounted by URL and exposed to a real headless Claude process through MCP. Each invocation was restricted with `--strict-mcp-config` to only:
285
110
 
286
- Use the core engine when you already have vectors and want an in-memory ANN
287
- index.
288
-
289
- ```js
290
- import Pikelet from 'pikelet-wasm';
291
-
292
- const index = await Pikelet.create({
293
- dim: 384,
294
- maxElements: 100000,
295
- metric: 'cosine',
296
- quantized: true,
297
- });
111
+ ```text
112
+ mcp__wiki-pack__search
113
+ mcp__wiki-pack__list_packs
114
+ mcp__wiki-pack__get_record
115
+ ```
298
116
 
299
- const docVector = new Float32Array(384).fill(0.1); // your embedding here
300
- const queryVector = new Float32Array(384).fill(0.1); // your query embedding here
117
+ No web tool. No filesystem tool. No alternate retrieval tool.
301
118
 
302
- const id = index.add(docVector);
119
+ One of the actual responses:
303
120
 
304
- const results = index.search(queryVector, 10);
305
- // [{ id: 0, distance: 0.12 }, ...]
121
+ > **Q: What causes earthquakes?**
122
+ >
123
+ > Earthquakes are caused by the movement of tectonic plates in the Earth's crust. Stress builds up along fault boundaries where plates grind against or ride over one another; when that stress exceeds the friction holding the locked fault in place, it suddenly releases as shock waves (seismic waves) that shake the ground [record 429637].
124
+ >
125
+ > There are three main fault types that can produce quakes — normal, reverse (thrust), and strike-slip — and some quakes have less clear-cut causes, like deep shifts in ancient rock or mine-collapse "tremors" [record 429638].
126
+ >
127
+ > Two related mechanisms compound this:
128
+ >
129
+ > * Subduction, where one plate is forced under another into the mantle, produces the most powerful earthquakes and can trigger tsunamis [record 132833].
130
+ > * More broadly, the slow convection-driven movement of crustal plates over the hot mantle is the underlying driver of quakes, volcanoes, and mountain formation [record 132267].
131
+ >
132
+ > Volcanic eruptions can also cause earthquakes, though these are localized to areas near active volcanoes [record 429637].
306
133
 
307
- index.delete(id);
308
- index.compact();
134
+ The model did not just repeat the first search hit. It retrieved multiple records and synthesized them into a sourced answer.
309
135
 
310
- const snapshot = index.export();
311
- const restored = await Pikelet.restore(snapshot, { maxElements: 100000 });
136
+ Other runs produced:
312
137
 
313
- index.dispose();
314
- restored.dispose();
138
+ ```text
139
+ Who was the first person on the moon? → Neil Armstrong (records 408686, 408684)
140
+ How do volcanoes form? → plates + subduction + hotspots
141
+ (records 132770, 132765, 132852, 132853)
142
+ How does photosynthesis work? → two-phase reaction, cited (5 records: 114219, 114220, 114222, 114223, 269279)
143
+ What is the capital of France? → Paris (records 454641, 302684)
315
144
  ```
316
145
 
317
- If you have records with vectors:
146
+ That is the product in one demonstration:
318
147
 
319
- ```js
320
- const rows = [
321
- { id: 'doc-1', vector: embedding1 },
322
- { id: 'doc-2', vector: embedding2 },
323
- ];
324
-
325
- const { index, idMap } = await Pikelet.fromVectors(rows, {
326
- metric: 'cosine',
327
- quantized: true,
328
- });
329
-
330
- const hits = index.search(queryEmbedding, 5);
331
- console.log(idMap.get(hits[0].id));
332
- ```
148
+ > **A model is interrogating a 456,153-record knowledge base whose backend is a static file.**
333
149
 
334
- On Node entrypoints, file helpers are available:
150
+ The model may already contain some of these facts in its pretrained parameters. This test demonstrates the retrieval, synthesis, citation, and deployment path; the [Veyra ablation](docs/veyra-ablation.md) tests whether support changes when evidence is removed from the pack.
335
151
 
336
- ```js
337
- const { index, idMap } = await Pikelet.loadJsonFile('vectors.jsonl', {
338
- metric: 'cosine',
339
- vectorKey: 'embedding',
340
- idKey: 'docId',
341
- maxFileBytes: 64 * 1024 * 1024,
342
- });
343
-
344
- const restored = await Pikelet.loadSnapshotFile('index.pnck', {
345
- dim: 384,
346
- maxElements: 100000,
347
- metric: 'cosine',
348
- quantized: true,
349
- });
350
- ```
152
+ **Network cost.** In a single persistent session (one mount, five queries, one repeat):
351
153
 
352
- ## Complete Artifact Quick Start
154
+ | Operation | Bytes transferred | Requests |
155
+ | ------------------------------- | ----------------: | -------: |
156
+ | Initial mount | 51.7 MiB | 11 |
157
+ | Query 1 + one-time encoder load | 25.9 MiB | 77 |
158
+ | Query 2 | 504.4 KiB | 37 |
159
+ | Query 3 | 499.4 KiB | 46 |
160
+ | Query 4 | 968.9 KiB | 75 |
161
+ | Query 5 | 1.1 MiB | 116 |
162
+ | Repeated query 1 | 238.4 KiB | 4 |
163
+ | **Total** | **80.8 MiB** | **366** |
353
164
 
354
- Use the complete reader when you want a file that is itself the search
355
- application data plane. To build one from your own docs, see the compile
356
- command above (`npx pikelet compile`).
165
+ Roughly **12.5% of the 648.5 MiB artifact** crossed the wire across that whole session. Excluding the one-time ~25 MiB encoder load, fresh-query traffic ran **~0.5–1.1 MiB per query**. The complete artifact was never downloaded.
357
166
 
358
- ```js
359
- import { openPikeletFile } from 'pikelet-wasm/complete'; // or './complete/index.mjs' in a checkout
167
+ The headless-Claude test above used a fresh process per question, so each invocation repaid the ~52 MiB mount and ~25 MiB encoder cost — about 78 MiB per cold query. That's a real operational distinction: **persistent sessions amortize mount and encoder cost; independent cold processes do not.**
168
+
169
+ *(The large benchmark fixture still carries its historical `.pancake` filename from before the Pikelet rename; current artifacts use `.pikelet`.)*
360
170
 
361
- const search = await openPikeletFile('examples/05-one-file-search/pancake-docs.pancake');
171
+ ---
362
172
 
363
- const out = await search.query('how do workers restore snapshots', { k: 5 });
173
+ ## Attach a pack to an LLM
364
174
 
365
- console.log(out.matchQuality, out.confidence);
366
- console.log(out.results[0].title);
175
+ `pikelet mcp` exposes one or more packs through the Model Context Protocol:
367
176
 
368
- await search.close();
177
+ ```bash
178
+ npx pikelet mcp install \
179
+ --client claude-code \
180
+ --pack https://example.com/docs.pikelet#<sha256>
369
181
  ```
370
182
 
371
- For kind-3 artifacts, no host encoder is needed:
183
+ The agent gets `search`, `get_record`, `list_packs`, `verify_pack`. A search result carries the identity of the pack and the location of the source record, so an agent can work against `product-docs.pikelet`, `rust-reference.pikelet`, `policy-2026-09.pikelet`, `customer-manual-v4.pikelet` without each publisher operating a retrieval API. The artifact can be local, private, public, behind authenticated object storage, or distributed like any other static asset.
372
184
 
373
- ```js
374
- const search = await openPikeletFile('pancake-wiki-inline.pancake');
375
- const out = await search.query('how do volcanoes form', { k: 5 });
376
- ```
185
+ A pack mounted with a content hash has a stable identity — `https://example.com/docs.pikelet#8d731...` — so "what body of knowledge did this agent query?" has a reproducible answer.
377
186
 
378
- For kind-2 artifacts, pass `options.encodeQuery`; the reader runs it
379
- against the declaration's verification vectors before serving and refuses
380
- the open if they disagree (`info().encoderVerified` reports the outcome).
187
+ **A mounted pack's content reaches the model as tool output.** `verify_pack` proves the bytes are intact and match their pinned identity; it does not prove the corpus itself is trustworthy. Mounting a pack from a source you don't control is the same trust decision as giving an agent any other untrusted-content tool — treat pack text the way you'd treat search results or fetched web pages, not as instructions.
381
188
 
382
- The reader also accepts a range source, which is how a `.pikelet` on R2 or
383
- a CDN gets queried without downloading it:
189
+ **Try it with your own questions.** The three Veyra packs from the [ablation](docs/veyra-ablation.md) are published as pinned GitHub release assets. Fetch and verify them first:
384
190
 
385
- ```js
386
- const search = await openPikeletFile({
387
- size,
388
- async read(offset, length) {
389
- // return bytes for [offset, offset + length)
390
- },
391
- });
191
+ ```bash
192
+ npm run demo:veyra
392
193
  ```
393
194
 
394
- On integrity: every artifact-derived read is bounds-checked and budgeted
395
- (`maxReadBytes`, `maxRecordBytes`). Format-2 files (`pancake-complete-v2`,
396
- the builder's default) verify each hydrated record against a per-record
397
- digest on its own range read (`info().corpusIntegrity`), and an embedded
398
- format-2 sketch verifies each lazily fetched index row the same way
399
- (`info().indexRowIntegrity`) — so a tampered transport fails the query
400
- instead of skewing its results. Older files with a format-1 sketch commit
401
- the lazy index rows to the identity but don't verify them per read; for
402
- those, pass `verifyIndexVectors: true` (or call `verifyVectors()`) to
403
- authenticate them in one full pass before trusting results from an
404
- untrusted transport (spec/COMPLETE_PROFILE.md section 6).
405
-
406
- ### Attaching packs to an LLM
407
-
408
- A complete artifact doubles as a portable knowledge pack: everything an
409
- LLM needs to query a body of knowledge — corpus, indexes, encoder,
410
- calibrated abstention, integrity commitments — in one immutable,
411
- identity-pinned file. `pikelet mcp` serves packs over the
412
- Model Context Protocol so Claude Code, Claude Desktop, or any agent
413
- framework can attach them as a retrieval tool with one config line — no
414
- vector database, no embedding service, no retrieval backend. Results
415
- carry full provenance (pack, manifest identity, title, heading path,
416
- source), and a pack that cannot answer says so instead of returning
417
- plausible noise. See the pikelet README's
418
- "Attaching packs to an LLM (MCP)" section.
419
-
420
- ## Demos
421
-
422
- Start with [DEMOS.md](DEMOS.md) for the maintained demo path.
423
-
424
- Useful commands:
195
+ Then mount all three through Pikelet's MCP server:
425
196
 
426
197
  ```bash
427
- npm run demo # synthetic engine validation
428
- npm run demo:artifact # range artifact search
429
- npm run demo:sketch # sketch artifact search/rerank path
430
- npm run test:browser # browser smoke test
431
- node examples/05-one-file-search/test-file.mjs
432
- node examples/05-one-file-search/test-wiki.mjs
433
- node examples/05-one-file-search/test-inline.mjs
198
+ claude -p "your question here" \
199
+ --mcp-config examples/one-file-search/web/public/veyra.mcp.json \
200
+ --strict-mcp-config \
201
+ --allowedTools "mcp__veyra-demo__search,mcp__veyra-demo__list_packs,mcp__veyra-demo__get_record"
434
202
  ```
435
203
 
436
- The one-file example is the closest thing to the project's current center
437
- of gravity:
204
+ For an interactive session, use the same config without `-p`:
438
205
 
439
206
  ```bash
440
- cd examples/05-one-file-search
441
- node compile.mjs
442
- node test-file.mjs
443
- node serve.mjs
207
+ claude \
208
+ --mcp-config examples/one-file-search/web/public/veyra.mcp.json
444
209
  ```
445
210
 
446
- ## API Snapshot
211
+ All three packs derive from the same small synthetic Station Veyra corpus, with controlled differences in the Tovash evidence. Ask where the Tovash project is housed: the full pack supports Chamber 17, the modified pack supports Chamber 43, and the ablated pack should abstain.
447
212
 
448
- Core index:
213
+ ---
449
214
 
450
- ```js
451
- const index = await Pikelet.create(options);
452
- index.add(vector);
453
- index.addBatch(vectors);
454
- index.search(query, k, options?);
455
- index.searchFiltered(query, k, allowedIds, options?); // allowedIds: Set of ids
456
- index.delete(id);
457
- index.compact();
458
- index.export();
459
- index.dispose();
460
- ```
215
+ ## Match quality and abstention
461
216
 
462
- Common options:
217
+ A nearest neighbour is not automatically evidence that a corpus answers a question. Pikelet can calibrate retrieval signals at build time (best semantic distance, distance margin, lexical coverage, retrieval agreement). When the corpus supports a reliable classifier, results carry `matchQuality: strong | weak | none`. When calibration can't separate supported from unsupported reliably — a single novel may be semantically homogeneous enough that the fit isn't trustworthy — Pikelet reports `matchQuality: unscored` and records why calibration was skipped, rather than manufacturing confidence. A `none` verdict withholds `results` by default; pass `query(text, { showAbstained: true })` to see the raw retrieval anyway — `matchQuality` and `confidence` are unaffected either way.
463
218
 
464
- ```js
465
- {
466
- dim: 384,
467
- maxElements: 100000,
468
- metric: 'cosine', // 'cosine' or 'l2'
469
- quantized: true,
470
- M: 12,
471
- efConstruction: 75,
472
- efSearch: 100,
473
- seed: 108
474
- }
475
- ```
219
+ `matchQuality` is evidence about retrieval support. It is **not** a guarantee that an LLM will never hallucinate.
476
220
 
477
- Snapshot helpers:
221
+ ---
478
222
 
479
- ```js
480
- await Pikelet.restore(bytes, options);
481
- await Pikelet.loadSnapshotFile(path, options);
482
- await Pikelet.loadJsonFile(path, options);
483
- await Pikelet.fromVectors(rows, options);
484
- ```
223
+ ## Deeper dives
485
224
 
486
- Range and sketch artifact APIs ship at `pikelet-wasm/artifact`; the
487
- complete-profile reader and builder ship at `pikelet-wasm/complete` and
488
- `pikelet-wasm/complete/builder`. The wiki-scale compilers and acceptance
489
- tests live in `examples/05-one-file-search/`.
225
+ - [How a `.pikelet` query runs](docs/how-a-query-runs.md) — what is inside the file, what a query costs on the wire, remote execution, and the integrity checks on the read path.
226
+ - [The Veyra ablation](docs/veyra-ablation.md) — what happens to answers when the supporting record is edited or removed from the pack, with the reproduction commands.
227
+ - [Why make knowledge a file](docs/why-a-file.md) — the reasoning behind the single-artifact design, and what it commits to.
228
+ - [Architecture](docs/architecture.md) — the engine, the C ABI and the JavaScript wrapper; the artifact formats are specified in [`spec/`](spec/).
490
229
 
491
- ## Building From Source
230
+ ## What this is not
492
231
 
493
- You only need this if you change the C++ engine under `src/`.
232
+ **Not a claim that vector databases are obsolete.** If your corpus changes continuously, needs transactional updates, serves many tenants, or already lives comfortably in a database, use a database. Pikelet targets `build → publish → query many times → replace on release`.
494
233
 
495
- Requirements:
234
+ **Not state-of-the-art embedding research.** The bundled model is MiniLM-L6, chosen for being small enough to live inside the artifact. ArguAna demonstrates a real quality cost from the compact encoder.
496
235
 
497
- - Node.js
498
- - Emscripten SDK
236
+ **Not "LLMs can no longer hallucinate."** The artifact exposes retrieved evidence, provenance, integrity, and an explicit support signal. Whether an agent obeys those signals is an agent behavior question.
499
237
 
500
- Build:
238
+ **Not a new HNSW algorithm.** HNSW is HNSW. HTTP Range is HTTP Range. BM25 is BM25. MiniLM is MiniLM. Affine quantization is not new either. The project is about what becomes possible when those pieces are arranged around one constraint: **the knowledge base itself must be distributable as a file and remain useful without dedicated retrieval infrastructure.**
501
239
 
502
- ```bash
503
- npm run build
504
- ```
240
+ ---
505
241
 
506
- Build scalar and SIMD variants:
242
+ ## When Pikelet is a good fit — and when it isn't
507
243
 
508
- ```bash
509
- npm run build:all
510
- ```
244
+ **Use it when:**
511
245
 
512
- The build emits `dist/engine.js`, `dist/engine.wasm`, and scalar variants.
246
+ - your corpus is static or changes on a release cycle
247
+ - you want semantic search without operating search infrastructure
248
+ - you want search inside a browser, Node process, edge worker, Electron app, or offline tool
249
+ - you want to distribute a searchable corpus to someone else
250
+ - you want an LLM agent to query a frozen, versioned body of knowledge
251
+ - static/object storage is easier to deploy than a database
252
+ - provenance and reproducibility matter
253
+ - a content-addressed knowledge snapshot is useful
513
254
 
514
- ## Tests
255
+ Examples: documentation, SDK/API references, technical manuals, research corpora, standards, legal texts, books, product knowledge, code/documentation snapshots, evaluation corpora, agent reference packs.
515
256
 
516
- Core (`npm test` also runs the artifact and complete-profile conformance
517
- suites):
257
+ **Don't use it if:**
518
258
 
519
- ```bash
520
- npm test
521
- npm run test:fuzz
522
- npm run test:simd
523
- npm run test:browser
524
- npm run test:scaffold
525
- ```
259
+ - your corpus has heavy online writes
260
+ - you need transactional mutation
261
+ - you need a multi-tenant authoritative search service
262
+ - you need exact nearest-neighbour search at very large scale
263
+ - your corpus is so large that a linear resident sketch scan is inappropriate
264
+ - you already operate a vector database happily and portability buys you nothing
526
265
 
527
- Complete profile, end to end:
266
+ The resident remote scan is linear in row count. The current architecture targets corpora through the low millions of records, not arbitrary web scale.
528
267
 
529
- ```bash
530
- node examples/05-one-file-search/test-file.mjs
531
- node examples/05-one-file-search/test-wiki.mjs
532
- node examples/05-one-file-search/test-inline.mjs
533
- ```
268
+ ---
534
269
 
535
- The inline wiki acceptance test downloads the large release artifact on
536
- demand when `examples/05-one-file-search/pancake-wiki-inline.pancake` is
537
- missing.
270
+ ## Repository map
538
271
 
539
- ## Performance Notes
272
+ ```text
273
+ packages/pikelet-wasm/ The published pikelet-wasm package:
274
+ engine/ C++ vector engine (HNSW, float32 and
275
+ affine-u8 backends, mutation, compaction,
276
+ snapshot import/export), compiled to WASM
277
+ in dist/. Used at compile time to build and
278
+ quantize the index; query time does not load
279
+ it (see complete/index.mjs's header comment)
280
+ — .pikelet reads run a pure-JS sketch scan,
281
+ optionally SIMD-accelerated.
282
+ src/ Engine wrapper and artifact readers/builders
283
+ (the entrypoints, core/, artifact/, errors/)
284
+ complete/ Reader and builder for the complete
285
+ range-readable .pikelet artifact
286
+ native/ N-API build of the same engine (benchmarks)
287
+ packages/pikelet/ The published pikelet CLI: compiler, MCP
288
+ server, encoder integration, Docusaurus plugin
289
+ docs/ Deeper dives (how a query runs, the Veyra
290
+ ablation, why a file), architecture notes,
291
+ rename history, measurement reports
292
+ spec/ Byte-level artifact contracts
293
+ benchmarks/beir/ Frozen BEIR ablation harness (encoder,
294
+ quantization, HNSW quality)
295
+ benchmarks/range-proof/ The deliberately boring static-HTTP proof:
296
+ dumb-server.mjs, proof.mjs, mcp-proof.mjs,
297
+ llm-proof.mjs, failure-modes.mjs
298
+ examples/one-file-search/ Large single-artifact search and embedded
299
+ encoder work
300
+ examples/mcp-knowledge-pack/ Compile, mount, search, and hydrate
301
+ records through MCP
302
+ local-packs/ Prebuilt example .pikelet artifacts used
303
+ by the demo commands above
304
+ packs/ Pack hosting and distribution examples
305
+ ```
540
306
 
541
- Performance is workload-dependent, but these are the knobs that matter:
307
+ ---
542
308
 
543
- - `quantized: true` for much smaller corpus-side vector storage.
544
- - `efSearch` for the recall/latency tradeoff.
545
- - sketch artifacts for depth-1 candidate selection before lazy rerank
546
- reads.
547
- - corpus layout and clustering for fewer remote read rounds.
548
- - kind-3 inline encoders when deployment shouldn't depend on an external
549
- ML runtime.
309
+ ## Reproduce the proofs
550
310
 
551
- The older engine-level benchmarks are still useful for HNSW behavior. The
552
- newer artifact-level benchmarks are more representative of what the project
553
- is actually for: search from static files and range-readable storage.
311
+ **Range proof** (`benchmarks/range-proof/`) is built around a server that does not understand Pikelet. It checks: static HTTP + Range + Pikelet client = remote semantic retrieval, without a full download. The main script reports artifact size, records, mount bytes/requests, per-query bytes/requests, cache behavior. `failure-modes.mjs` independently exercises Range supported / ignored / tampered bytes / truncated response. `mcp-proof.mjs` and `llm-proof.mjs` use a real MCP client and a real headless Claude process rather than a mocked model response.
554
312
 
555
- The native baselines the engine benchmarks compare against (faiss-node,
556
- hnswlib-node, usearch) are not dependencies of `pikelet-wasm`; install
557
- them on demand with `cd benchmarks && npm install` before running those
558
- scripts. See `benchmarks/`, `benchmark_results/`, and the example READMEs
559
- for current numbers.
313
+ **BEIR evaluation** (`benchmarks/beir/`) keeps stages separate so a quality change can be attributed to the component that caused it, plus a standalone conformance fixture checking the JS benchmark path against the actual C++ affine-u8 representation. Don't read the latency columns as a universal native-performance comparison — they measure the benchmark paths under the stated harness. The quality deltas are the important part.
560
314
 
561
- ## Tradeoffs
315
+ ---
562
316
 
563
- Pikelet is a good fit when:
317
+ ## Status
564
318
 
565
- - you want search in JavaScript runtimes without native addons;
566
- - you want static-file or edge deployment;
567
- - you need reproducible, inspectable search artifacts;
568
- - your corpus can be built offline;
569
- - byte-range reads are cheaper than operating a dedicated search service.
319
+ Pikelet is early. The implementation is real; the format is not frozen. One primary author. Draft 2 artifact format.
570
320
 
571
- It's the wrong tool when:
321
+ - prebuilt WASM included
322
+ - local and HTTP readers
323
+ - affine-u8 and float HNSW backends for compile-time indexing/quantization
324
+ - embedded query encoder
325
+ - BM25 hybrid retrieval
326
+ - content identity and lazy-read integrity verification
327
+ - calibration/abstention support
328
+ - MCP mounting
329
+ - BEIR retrieval evaluation
330
+ - range-read and failure-mode tests
331
+ - a large 456k-record example artifact
572
332
 
573
- - you need high-rate online mutation;
574
- - you need a multi-tenant database server;
575
- - you require exact nearest-neighbor search at large scale;
576
- - your deployment can already use a mature native ANN service with no
577
- portability constraints.
333
+ `npm test` exercises the engine, artifact profiles, MCP, ingestion, format hardening, and related conformance suites.
578
334
 
579
- ## Status
335
+ Previously known as **Pancake** (renamed September 2026); artifacts, profile strings and files from before the rename remain readable — see [`docs/history.md`](docs/history.md) for what kept the old name and why. Pikelet is unrelated to the pre-existing Pikelet programming language.
580
336
 
581
- The engine and the artifact readers/builders are published and usable
582
- today (`pikelet-wasm`, with `pikelet` as the app scaffold).
583
- The complete `.pikelet` profile's spec is still marked draft — Draft 2,
584
- under review, not frozen — but its reader and builder ship in the package,
585
- and it is the clearest expression of where the project is going: a search
586
- engine packaged as one verified, range-readable file.
337
+ ---
587
338
 
588
339
  ## License
589
340