| title | B2 — Read me / map | ||||
|---|---|---|---|---|---|
| type | note | ||||
| tags |
|
||||
| created | 2026-06-29 | ||||
| status | draft |
A personal, local-first knowledge vault — plain Markdown you fully own — with an AI layer that surfaces the semantically similar notes you haven't linked yet, so you can commit the typed, explained connections between them yourself.
Status: the design is locked and the index engine is built (
crates/b2-core: steps 0→5 of the index engine). Theb2CLI over a typed core API is live (crates/b2-cli): point B2 at a folder andreindex/search/neighbors/explainit from the terminal, with--jsonfor agents. Semantic search is real (crates/b2-embed: a candle-backed local embedder behind the one seam;b2 initdownloads the model into a shared cache; the fake stays the CI default). Connection discovery ships asb2 similar(surface the nearest unlinked notes — local, free, no model call) +b2 link(you commit a typed relation to frontmatter) — the human is the precision gate; there is no LLM in the loop. A tour grounded in the test suite: docs/architecture.md.Grounded chat is live in the CLI —
b2 ask "…"andb2 chatanswer questions from your notes, streaming, with[n]citations back to the notes the answer came from (#151). It talks to any OpenAI-compatible model server (crates/b2-llm, a hand-rolled sync SSE client — nothing in B2 is async, and theb2binary links no tokio); Ollama is the guided default, a cloud model is explicit opt-in, and nothing about a chat is written to your notes or the index. The chat pane in the desktop app is next.The desktop app has shipped — a Tauri app (
crates/b2-desktop, the second dumb adapter over the façade) + a Vite + vanilla-TS frontend (ui/), talking to the core over Tauri IPC. The read → discover → link → edit → reconcile arc is complete: read a note on the left, commit a typed link to its similar-but-unlinked notes on the right with a click, edit the body in place (CodeMirror 6 with live preview, autosave, and a revision-guarded conflict bar), and a native fs-watch reconciles external edits live. Indexing is automatic in the app — on open, and on every fs-watch pulse — so there is noreindexto remember; the manual one lives in Settings as a cancellable background action (live progress, a Cancel button, the UI usable throughout). Projection and embedding are decoupled, so a cold vault is browsable/keyword-searchable in seconds while embedding streams behind (#15). Run it withmake app— pick a vault from the in-app switcher, or skip straight to one viaB2_VAULT_PATH. Next: file-type support (resources) — slice 1, inventory & graph, is built; the wider backlog lives in GitHub Issues.
Point B2 at a folder of Markdown notes and it becomes a second brain that thinks alongside you: it reads everything, builds a typed graph, and keeps surfacing the similar notes you haven't connected yet — so the structure of your knowledge grows as you link them, instead of rotting. The files stay plain Markdown on your disk, yours forever; B2 is the intelligence layer over them, not a container around them. Humans and AI agents are both first-class users.
Full motivation, scope, and locked decisions: docs/invariants.md.
Two architectural tenets shape every decision (full text: docs/invariants.md):
- A volatile vault over a disposable index. Refactor fearlessly — move, split, merge, compress,
trim orphans. The index is a pure projection of your vault (drop it, rebuild it identical);
nothing durable B2 derives lives outside your notes (
index = projection of (the vault directory)). Idempotency is the mechanism; a vault you can rewrite without fear is the point. - Build for tomorrow's model (the Bitter Lesson). Every AI part sits behind a swappable seam; we orchestrate the minimum today's model needs and no more — so a more capable model is a drop-in, not a redesign.
…in service of five product non-negotiables — plain-Markdown source of truth · local-first · zero lock-in · AI-native (not bolted-on) · single binary (docs/invariants.md).
Everything lives in docs/ — one page per topic, and that page is the map. New here? Start with the Quick start — set up and work with a vault in about ten minutes. Then go deeper: architecture · search & similarity, in plain language.
| Doc | What it owns |
|---|---|
| docs/invariants.md | The invariant register — the one-page normative list of what must always be true, and the source of why, cited by id. On conflict with any other doc, it wins. |
| docs/data-model.md | What a note and a connection are, in plain Markdown · the two storage tiers · the relation vocabulary · the invariant definitions. The canonical what. |
| docs/index-engine.md | How the derived index is built and queried — SQLite (FTS5 + an in-process vector scan) as a disposable projection, and the four flows over it. The canonical how. |
| docs/quickstart.md | Set up and use B2: the walkthrough, the command reference, config and every environment variable. |
| docs/architecture.md | How the system is built: the crates, the flows, the seams, the tests. |
| docs/search-and-similarity.md | What search and the related-notes panel do, in plain language, for everyone who uses B2. |
| ADRs/ | Architecture Decision Records — one terse record per key architectural choice: the context, the ruling, and what it costs. The why behind the register's entries. |
| docs/evals.md | The eval suite guide — every instrument and how to read it, corpora, labels, the exit gate, process rules, and the record of every measured verdict. |
Planned work and the backlog live in GitHub Issues; shipped build history lives in git.
Stop 0 — check your setup. On a fresh clone, run make doctor first: it checks Rust,
Node/npm, the Tauri CLI, and the platform build toolchain, and prints the fix for anything
missing (this is the fastest path to a working make app — see the desktop app section below).
make doctorcargo install --path crates/b2-cli --locked # installs `b2` to ~/.cargo/bin (on PATH)
b2 --helpThis puts a real b2 on your PATH. Re-run it (add --force) or make install to update after code changes.
For engine iteration where you don't want to reinstall each time, cargo run -p b2-cli -- … runs in place.
A Makefile wraps this and the other common commands — needs no separate install, make ships
with the platform build toolchain (Xcode Command Line Tools on macOS):
make doctor # sanity-check your local setup — run this first on a fresh clone
make install # build + install `b2` onto your PATH (~/.cargo/bin)
make test # fast, deterministic, model-free engine suite
make check # THE FAST GATE (~3s): fmt-check + clippy (-D warnings) + engine & frontend
# tests — the loop you run while working
make ci # THE COMPLETE GATE (~18s): the above over the whole workspace, plus the
# desktop crate, every test in the repo, and an `npm audit` of ui/.
# GitHub Actions runs this exact target, so green here is green there.
make init # download + verify the embedding model into the shared cache
make eval # semantic-retrieval quality eval (real model; never part of CI)
make eval-chat # grounded-chat quality eval (needs a model server; never part of CI)
make # list every target, grouped: setup / dev / gates / coverage / modelThere is no git hook and none is wanted (.git/hooks isn't cloneable) — CI is the enforcement,
and make ci is how you get the same answer before you push.
The desktop app. Prerequisites: Node + npm (for the ui/ frontend — Node 18, 20, or 22+;
that floor comes from vite, nvm is the easiest way to install
one: nvm install --lts) and the Tauri CLI (cargo install tauri-cli --locked). Run
make doctor first — it checks both (including the Node version, and whether nvm is
available if Node is missing), plus the platform build toolchain (Xcode Command Line Tools on
macOS), and tells you exactly what's missing and how to fix it (this is what catches e.g.
make app failing with error: no such command: 'tauri' before it happens).
make doctor # confirm Node, npm, and the Tauri CLI are all in place
make app # dev run (Vite HMR + a live window); Metal GPU on Apple Silicon
make app-cpu # …same, but force the CPU embedder
make app-build # bundle a per-platform app
make ui-install # (rarely needed by hand — every recipe above depends on it)On first launch (nothing remembered yet) the window opens with no vault selected — click the
vault switcher to pick one; from then on, make app reopens whatever vault you had open last.
B2_VAULT_PATH (or a launch argument) is an alternative for that first run, letting you skip
the picker and jump straight into a vault, e.g. B2_VAULT_PATH=~/notes make app. Search or use
the file tree to open a note, read or edit it on the left (live-preview Markdown, autosave), and
connect its similar-but-unlinked notes from the right pane. Set B2_EMBEDDER=fake for an
offline, non-semantic dev mode (no b2 init needed).
Embedder device — CPU or Metal GPU. make app senses the platform and embeds on the Metal GPU
on Apple Silicon (measured ~7× faster than CPU on the test vault — GH #40),
falling back to CPU automatically if the GPU can't initialize. make app-cpu forces the CPU embedder
(the A/B counterpart, or if you hit a GPU issue). The active device is shown as a subtle badge in
Settings (⌘,). Metal is a compile-time choice, so the recipe you run picks it — and because CPU
and Metal produce distinct vectors, switching device re-embeds the vault on the next reindex (a one-time
model swap; search refuses to mix the two). See fixtures/README.md and
make compare-device to benchmark the two on your own hardware.
Point B2 at a vault with -C <path> (a.k.a. --vault) on any command, or set B2_VAULT_PATH once so
every command finds it without the flag (an explicit -C wins). Read-only commands (search,
neighbors, …) fall back to the current dir; commands that write (reindex, add, mv, link) require an
explicit vault and refuse otherwise, so they can't silently touch the wrong place. Full walkthrough:
Quick start.