breadsearch/DESIGN.md
Breadway c6ed6a41d8
All checks were successful
Mirror to GitHub / mirror (push) Successful in 1s
release / build (push) Successful in 2m12s
Add OpenVINO backend for Intel iGPU/dGPU (Arc)
Mirrors the rocm/cuda pattern: ort::ep::OpenVINO with device_type "GPU"
(covers both Intel integrated graphics and Arc discrete GPUs through the
same EP), load-dynamic/dlopen so no OpenVINO toolkit is needed at build
time, and its own --openvino flag / backend = "openvino" config value.
Folded into the `full` feature alongside npu/rocm/cuda.

OpenVINO's provider options go through a generic key/value FFI interface
rather than a fixed C struct (unlike MIGraphX's OrtMIGraphXProviderOptions),
so it should be less exposed to the ABI-version-skew crash MIGraphX hit --
but that's inference from the EP's design, not verified against real
hardware. Like CUDA, this is compile-checked only: no Intel GPU in this
dev environment to runtime-verify against.

Version bump: breadmill 0.2.2 -> 0.2.3.
2026-07-03 22:58:27 +08:00

8.4 KiB

breadsearch + breadmill — semantic system-wide search for BOS

Context

BOS/bread has no content search — only breadbox's app launcher (exact/fuzzy over .desktop files). The goal is a semantic "find anything by meaning" engine: a flagship, differentiating BOS feature that's also the right workload for the AMD XDNA NPU (small encoder model, compute-bound single forward pass, always-on background embedding — none of the bandwidth-bound problems that make LLMs a bad NPU fit).

Two components, mirroring the breadpad/breadman split and the breadbox/breadbox-sync precedent (GUI + background helper + shared lib in one repo):

  • breadmill — always-on daemon: walks files → extracts text → chunks → embeds → vector index; answers queries over a Unix socket. ("mill grain into flour.")
  • breadsearch — standalone GTK4 GUI, forked from breadbox's UI, that queries breadmill and shows ranked hits. ("sift the flour.")

breadbox stays a pure app launcher, unchanged.

Decisions (confirmed with user)

  • Index scope (v1): curated roots — ~/Documents, ~/Projects (notes/docs, not code yet), ~/.config/breadpad. Extract md, txt, org, pdf, odt, docx. Skip binaries/images/build dirs/.git.
  • Repo layout: one cargo workspace at ~/Projects/breadsearch/.
  • Embedding model: nomic-embed-text-v1.5 (768-dim ONNX, ~550MB). Requires task prefixes: search_document: for indexed chunks, search_query: for queries; mean-pool + L2-normalize.
  • Compute: CPU-first; NPU (XDNA via ONNX Runtime VitisAI EP) is a later backend swap, not a v1 dependency.

Workspace layout

~/Projects/breadsearch/
  Cargo.toml              # [workspace] members = breadsearch-shared, breadmill, breadsearch
  bakery.toml             # bread package manifest (binaries: breadsearch, breadmill)
  config.example.toml     # ~/.config/breadsearch/config.toml template
  README.md
  breadsearch-shared/       # lib: XDG paths, config, IPC types + socket client
  breadmill/              # daemon bin
  breadsearch/              # GUI bin (forked breadbox UI)

Component: breadsearch-shared (lib)

Model on breadbox/breadbox-shared/src/lib.rs (XDG helpers + serde/toml config).

  • Paths: config_dir()~/.config/breadsearch; state_dir()~/.local/state/breadsearch (index); cache_dir()~/.cache/breadsearch (models); socket_path()$XDG_RUNTIME_DIR/breadmill.sock.
  • Config (serde + toml): [index] roots, extensions, max_file_mb; [search] limit, snippet_len; [model] name, dim.
  • IPC types (serde_json, newline-delimited JSON over the Unix socket):
    • Request: Query { query: String, limit: usize }, Status, Reindex.
    • Response: Hits(Vec<Hit>) where Hit { title, path, snippet, score }; StatusInfo { indexed, pending, model_ready }.
  • Socket client helper used by the GUI (connect, send, read one response).

Component: breadmill (daemon)

Pipeline, isolated behind small traits so each stage is swappable:

  1. Walkignore crate (parallel, respects .gitignore) over configured roots; filter by extension + size.
  2. Extractmd/txt/org: read directly; pdf: pdf-extract; docx/odt: unzip + strip XML (zip + quick-xml), best-effort.
  3. Chunk — ~512-token windows with overlap; keep byte offsets for snippets.
  4. EmbedEmbedder trait. v1 impl: ort (ONNX Runtime 2.x, CPU EP) + tokenizers (HF) running nomic-embed-text-v1.5. Apply search_document:/search_query: prefixes, mean-pool, normalize.
  5. Storerusqlite for metadata (path, mtime, content-hash, chunk text/offsets) keyed by rowid + usearch (HNSW, 768-dim, cosine) for vectors keyed by the same id. Both persisted under state_dir().
  6. Incremental — on start, diff roots against sqlite (mtime+hash): embed new/changed, drop deleted. Then live-watch with notify (debounced) to re-embed on change.
  7. Servetokio (or std threads) Unix-socket listener: Query → embed query → usearch top-k → join sqlite metadata → Hits. Also Status/Reindex.
  • Model fetch: first run downloads model.onnx + tokenizer.json from HF into cache_dir()/models/ (needs network once); breadmill --fetch-model to pre-fetch. Log clearly if absent.
  • Lifecycle: systemd user service breadmill.service (pattern from breadbox-sync.service / breadd), WantedBy=default.target.

Component: breadsearch (GUI) — fork of breadbox

Start from breadbox/breadbox/src/main.rs. Reuse verbatim: the gtk4-layer-shell overlay window (rename namespace/app-id to breadsearch / com.breadway.breadsearch), SearchEntry + ScrolledWindow + ListBox, ↑/↓/Enter/Esc handling, click-outside-to-close, PID-toggle (breadsearch.pid), and the theming path: bread_theme::gtk::apply_shared() + apply_app_css(|| build_css(&load_palette())) + user style.css. Pin bread-theme git tag v0.2.8, feature gtk (same as breadbox). Swap:

  • Result source: delete load_sorted_entries/fuzzy_*/DesktopEntry. On search.connect_changed, debounce ~150ms (glib::timeout_add_local) then query breadmill off the UI thread (std::thread + glib::MainContext::channel), clear the ListBox, append a row per Hit.
  • Row content: title (filename/heading) + muted path + snippet line; filetype icon via gio::content_type_guessImage::from_gicon. Extend build_css with a .hit-snippet class.
  • Action: replace do_launch with open-file — Enter/row-activated → xdg-open <path>; Ctrl+Enter → open containing folder. Then close.

Key crates

ort (ONNX Runtime), tokenizers, usearch, rusqlite, ignore, notify, pdf-extract, zip+quick-xml, serde/serde_json/toml, gtk4 0.11 + gtk4-layer-shell 0.8, bread-theme (git tag v0.2.8).

Packaging & BOS integration (last phase — post-1.0, per earlier decision)

  • bakery.toml (model on breadbox/bakery.toml): binaries = ["breadsearch","breadmill"], system_deps for onnxruntime/gtk; [[service]] unit="breadmill.service" enable=true; [config] dir="~/.config/breadsearch".
  • BOS: add breadsearch+breadmill to build-local.sh BREAD_BINS; autostart breadmill.service; Hyprland keybind (e.g. SUPER+slash) → breadsearch in the skel hyprland.lua.
  • Release: dual remotes (origin GitHub + forgejo), bakery index regen — per the bread release train.

Phasing (de-risked: ship CPU, NPU later)

  1. Scaffold workspace + breadsearch-shared (paths, config, IPC types, socket client).
  2. breadmill CPU pipeline end-to-end (walk→extract→chunk→embed→store→serve) + --reindex/--fetch-model + systemd unit.
  3. breadsearch GUI fork (socket query + xdg-open + theme).
  4. Packaging (bakery, config.example, README) + BOS wiring.
  5. Later: NPU Embedder impl (ort VitisAI/XDNA EP) — the go/no-go POC; pure backend swap.

Verification

  • breadmill --fetch-model then --reindex over a small test corpus; log embedded-chunk count; confirm state_dir index persists across restart.
  • Query the socket directly (a breadmill query "..." subcommand or socat) and confirm semantically-relevant hits with sane scores for a concept query (not keyword).
  • Launch breadsearch, type a concept (e.g. "tax stuff", "that suspend bug fix"), see relevant files ranked, Enter opens via xdg-open, Ctrl+Enter reveals folder, Esc closes; theme matches breadbox; hot-reloads on bread-theme reload.
  • Edit/add/delete a file in a root → notify re-index → new content findable within seconds.

Notes / risks

  • nomic prefixes + mean-pool + normalize must match between index and query or recall collapses.
  • ort linking: prefer the crate's downloaded/bundled ONNX Runtime to avoid version skew with Arch's onnxruntime.
  • Office formats (docx/odt) are best-effort in v1; md/txt/org/pdf are the reliable path.
  • GPU EPs (ROCm/CUDA/OpenVINO) fail to register silently at the ONNX Runtime level and fall back to CPU — always check startup logs for Successfully registered before trusting a GPU build is actually accelerating. See README: GPU backend notes for the MIGraphX-vs-ROCMExecutionProvider distinction and the per-shape JIT-compile-and-cache behavior that matters for interactive query latency.
  • CUDA and OpenVINO are compile-checked only — no NVIDIA or Intel GPU hardware in this dev environment (AMD-only) to runtime-verify against, unlike ROCm which was confirmed end-to-end on real hardware.