Skip to content

About

Semantic search over your Bear notes, running entirely on-device and exposed to AI agents through MCP.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

bear-app-rag

CI OpenSSF Scorecard codecov Tag License: MIT

Local-first semantic search for Bear notes. It reads Bear's SQLite database without modifying it, builds a local ONNX vector index, and lets agents retrieve through MCP.

Minimal by design. Two production dependencies. No orchestration framework. An eval shows where semantic search beats keyword matching and where it does not.

If you would rather read the long-form material in one place, it is published at fairbearlab.github.io/bear-app-rag: the architecture tour, the story of building it, what the eval does and does not measure, and the eight decision records. The benchmark charts only render there.

Why this exists

I keep enough material in Bear that remembering the idea is easier than remembering the words I used. Bear has no public API, and keyword search cannot connect a query such as "software releases without downtime" with a note about blue-green deployments unless the wording happens to overlap.

Sending an entire vault to a model is not a retrieval strategy. This project reads the local database, splits notes along their Markdown structure, embeds the chunks on-device, and returns only the relevant excerpts.

On the committed synthetic eval, semantic retrieval improves paraphrase recall from 0.60 to 1.00. The control group ties at 1.00, and the multi-concept group is a useful reminder that vector search does not win every ranking contest.

Honestly this served as a learning project for me and Shiny Frog has since released a first-party MCP that will likely serve you better long-term.

Quickstart

Prerequisites: macOS, Python 3.11+, and Bear with at least one note.

pip install git+https://github.com/fairbearlab/bear-app-rag.git
bear-rag index

The first index downloads the roughly 90 MB embedding model. Later indexing and search can run offline.

MCP server

The MCP server is the main interface. It lets a connected agent search, browse, read, and sync notes during a conversation. For a source checkout, add this to .mcp.json:

{
  "mcpServers": {
    "bear-notes": {
      "command": "uv",
      "args": ["--directory", "/path/to/bear-app-rag", "run", "bear-rag-mcp"]
    }
  }
}

This configuration is tested with Claude Code. Other MCP hosts can use the same stdio command, but their configuration shape may differ.

Auto-sync with cron

*/15 * * * * cd /path/to/bear-app-rag && uv run bear-rag sync --quiet >> ~/.bear-rag/sync.log 2>&1

How it works

flowchart LR
    subgraph local["On-device"]
        DB[("Bear SQLite DB<br/>(read-only)")] --> BR["BearReader<br/>bear_reader.py"]
        BR --> CH["Chunker<br/>chunker.py"]
        CH --> NS["NoteStore<br/>ChromaDB + local ONNX embeddings"]
        NS --> MCP["MCP server<br/>6 tools"]
        NS --> CLI["CLI<br/>index / sync / status / demo"]
    end
    MCP -. "returns selected note content" .-> Agent["Connected agent<br/>(opt-in trust boundary)"]
Loading

The installed package has no cloud SDK. Indexing, sync, and search do not initiate network requests after the one-time model download. ChromaDB telemetry is disabled through both its runtime settings and an import-time environment default.

That boundary ends where the MCP handoff begins: an agent receives your note content and may use its own network services. The optional development-only LLM judge also calls Anthropic when explicitly enabled. Those are opt-in boundaries, not properties of the indexing path. ADR-0002 records the full scope.

Known upstream advisories. ChromaDB 1.5.9 (the latest release, no fixed version available) carries four open advisories, all scoped to the ChromaDB HTTP server: PYSEC-2026-311 / CVE-2026-45829 (pre-auth code injection via trust_remote_code), PYSEC-2026-3813 / GHSA-2wm9-hf6c-p5cr (cross-tenant RBAC bypass), PYSEC-2026-3814 / GHSA-36p7-vc44-83pf (authenticated code injection via trust_remote_code on the collection-update endpoint), and PYSEC-2026-3815 / GHSA-xph7-9rjv-w5fr (SimpleRBACAuthorizationProvider ignores tenant/db/collection scope). This project uses chromadb.PersistentClient in-process (bear_rag/store.py) and never starts or exposes the HTTP server, so none of the vulnerable REST endpoints are reachable. CI's pip-audit step ignores all four IDs with a link back here; each ignore is removed once a fixed release ships.

Read the architecture tour.

Benchmark results

The committed eval compares local semantic retrieval with a SQLite LIKE baseline on 25 synthetic notes and 23 queries. Twenty queries cover exact match, synonym, paraphrase, and multi-concept retrieval. Three more exercise tag-filter correctness end to end.

Results come from tests/eval/results.json. All three reported metrics are deterministic and higher is better:

  • Recall@5: fraction of expected notes present in the top five results.
  • MRR: reciprocal rank of the first expected result.
  • Groundedness: fraction of expected keywords present in retrieved text.
Metric Semantic Keyword (LIKE)
Recall@5 0.90 0.75
MRR 0.91 0.79
Groundedness 0.87 0.80

By query type

Query type Count Recall semantic Recall LIKE MRR semantic MRR LIKE
exact_match 5 1.00 1.00 1.00 1.00
multi_concept 5 0.83 0.73 0.84 0.90
paraphrase 5 1.00 0.60 0.77 0.44
synonym 5 0.83 0.70 1.00 0.70
tag_multi_or 1 1.00 0.67 1.00 1.00
tag_no_match 1 1.00 1.00 1.00 1.00
tag_single 1 0.33 0.33 1.00 1.00

The core result: semantic retrieval is much better when the query paraphrases the note, but no better when the exact words are already present. On multi-concept queries, semantic retrieval finds more expected notes but ranks its first relevant result slightly lower. That is a tradeoff worth reporting, not rounding away.

Side-by-side examples

Query: "What makes products easy to use without reading instructions?"

  • Semantic returns: Recipe Ingredient Tracker, The Design of Everyday Things, Road Trip Planning
  • Keyword returns: Atomic Habits, Code Review Checklist, Budget Backpacking
  • The relevant note discusses affordances and signifiers. Keyword search cannot recover a concept whose query vocabulary never appears in the note.

Query: "How should I write software interfaces that other developers will enjoy using?"

  • Semantic returns: The Pragmatic Programmer, Deploying Python Apps, API Design Best Practices
  • Keyword returns: The Design of Everyday Things, Atomic Habits, Thai Green Curry
  • Semantic search places the API design note in the results; keyword search finds unrelated uses of "design" and "interfaces."

See the charts or read the evaluation methodology.

Design decisions

The ADRs record the choices and their limits:

Read the longer engineering narrative.

CLI reference

bear-rag index              # Wipe and rebuild the index
bear-rag sync               # Incremental update from Bear
bear-rag sync --dry-run     # Preview updates and deletions
bear-rag sync --quiet       # Print only when something changed
bear-rag status             # Show index statistics and last sync time
bear-rag demo               # Run a self-contained demo without Bear

Project structure

Module Purpose
bear_reader.py Read notes, tags, and live note state from Bear's SQLite database
chunker.py Split Markdown along headings with bounded overlap
store.py Persist and query ONNX embeddings through ChromaDB
sync.py Update changed notes and reconcile stale indexed notes
mcp_server.py Expose search, read, browse, sync, and status tools
cli.py Provide index, sync, status, and demo commands
status.py Read shared index and sync-state statistics
demo.py Run the small, self-contained retrieval demonstration
config.py Define paths, chunk settings, model choice, and telemetry defaults
models.py Define notes, chunks, metadata, and sync results

Configuration

The defaults live in bear_rag/config.py:

Constant Default Purpose
BEAR_DB_PATH Bear's group-container SQLite path Read-only source database
DATA_DIR ~/.bear-rag Local state directory
CHROMA_DIR ~/.bear-rag/chroma Vector index storage
EMBEDDING_MODEL all-MiniLM-L6-v2 Documented production model
MAX_CHUNK_WORDS 300 Maximum target chunk size
MIN_CHUNK_WORDS 30 Merge threshold for small sections
OVERLAP_WORDS 40 Context repeated across oversized splits

Development

git clone https://github.com/fairbearlab/bear-app-rag.git
cd bear-app-rag
uv sync --extra dev
uv run pytest -v
uv run pytest -m eval -v
uv run bear-rag demo

The optional LLM judge requires ANTHROPIC_API_KEY and an explicit flag. Inject the key from a secret manager rather than a plaintext .env; .env.example holds a 1Password CLI reference that op run resolves at run time:

op run --env-file=.env.example -- env EVAL_LLM_JUDGE=1 uv run pytest -m eval -v

make check runs lint, type check, tests, and the eval — the same gates CI enforces. See the Makefile and CONTRIBUTING.md.

The alternate-model research harness has a separate extra:

uv sync --extra research
uv run python tests/eval/embedding_sweep.py --list

License

MIT

About

Semantic search over your Bear notes, running entirely on-device and exposed to AI agents through MCP.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages