Bunker OS ships a complete text retrieval system built on BM25 — the same probabilistic ranking algorithm behind Elasticsearch and Lucene. It runs entirely on Python’s standard library, requires no API keys, needs no network access, and imposes zero external dependencies. You can index your entire wiki and search it offline in milliseconds.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/SamBleed/opencode-obsidian/llms.txt
Use this file to discover all available pages before exploring further.
What BM25 Is
BM25 (Best Match 25) is a sparse keyword retrieval algorithm. It scores documents by how well their term frequencies match a query, adjusted for document length and corpus-wide inverse document frequency. Unlike embedding-based retrieval, BM25 never needs a model inference step: it scores mathematically from a pre-built inverted index. The Bunker implementation uses BM25 parametersk1 = 1.5 and b = 0.75 — the standard defaults used by most production search engines.
Why BM25, Not Embeddings
Zero Dependencies
Pure Python 3.10+ stdlib. No numpy, no torch, no sentence-transformers, no API keys.
Fully Offline
The index lives at
.vault-meta/bm25/index.json. Search works with no network access.No Latency
Index queries run in local memory. No round-trip to any embedding API or inference server.
Agent Synthesizes
OpenCode is the only intelligence. BM25 returns ranked text chunks; the agent reads and synthesizes answers.
Retrieval Pipeline
The query never reaches an LLM for ranking. BM25 handles retrieval; OpenCode handles reasoning. The key principle: BM25 returns ranked chunks. OpenCode is the only intelligence in the loop — it reads those chunks and synthesizes the answer.Performance Characteristics
| Metric | Value |
|---|---|
| Chunk size | ~500 tokens at paragraph boundaries |
| Seed vault pages | 8 (grows with every ingested source) |
| Index format | .vault-meta/bm25/index.json |
| Chunks directory | .vault-meta/chunks/ |
| Runtime dependencies | Python 3.10+ stdlib only |
| BM25 k1 | 1.5 |
| BM25 b | 0.75 |
CLI Usage
All retrieval operations go throughscripts/retrieve.py:
Output Format
retrieve.py emits JSON to stdout. OpenCode reads this output and synthesizes an answer from the ranked chunks:
| Field | Description |
|---|---|
doc_id | Internal document identifier |
score | BM25 relevance score (higher is more relevant) |
path | Repo-relative path to the source wiki page |
chunk | Chunk index within the page (0-based) |
preview | First 200 characters of the chunk text |
Environment Variables
By defaultretrieve.py uses the repo root as the vault. Override it for cross-project use:
BUNKER_HOME and VAULT_PATH are supported. BUNKER_HOME takes precedence.
Indexer Internals (bm25-index.py)
The indexer inscripts/bm25-index.py handles three operations: build, query, and status.
Chunking Strategy
Pages are chunked at paragraph boundaries (double newlines) with a target of 500 tokens per chunk:Tokenization
BM25 Scoring
For each query termqt across each document doc_id:
N is total chunk count, df(qt) is document frequency of the term, dl is the chunk length, and avgdl is the average chunk length across the corpus.
Index Layout
index.json contains:
N— total chunk countavgdl— average document length in tokensterm_doc_freq— mapping of term → number of chunks containing itdoc_terms— mapping of doc_id → term frequency mapchunks— array of chunk metadata (path, chunk index, preview)stats— total pages, chunks, and vocabulary size
What Gets Indexed
The indexer scanswiki/**/*.md recursively but excludes wiki/meta/ to avoid indexing integrity reports and manifests.
When to Use BM25 vs Autoresearch
| Scenario | Use |
|---|---|
| ”What do we know about X?” — topic already in the wiki | BM25 (retrieve) |
| “How does the AOC pipeline work?” — documented concept | BM25 (retrieve) |
| “What is the latest on quantum computing?” — new topic | Autoresearch |
| ”Find all pages mentioning n8n webhooks” | BM25 (retrieve) |
| “Research and file a summary of SLSA Level 2” | Autoresearch → then BM25 after ingestion |
Validate with make test-retrieve
The test suite includes a two-test BM25 suite:index.json exists (via retrieve.py status). Test 2 runs a test query and confirms results are returned.
Related Pages
CLI Scripts
Shell scripts that complement the Python retrieval toolchain.
Testing
The full test suite including the BM25 retrieval suite.
Wiki Ingest
How sources get ingested into the wiki that BM25 indexes.
Wiki Query
The OpenCode skill that uses BM25 results to synthesize answers.