Requirements — second-brain retrieval layer v2

At a glance

  • What it is: upgrade search_vault from semantic-only search to a schema-aware hybrid retrieval layer — hard filters + semantic ranking in one query, schema exposed to the agent, scoped read-only endpoints
  • Status: Draft v2 — R-1 resolved after vault-indexer code review; Phase 0 added, frontmatter filter descoped
  • Depends on: vault-indexer (metadata extraction — requires Phase 0 changes below), second-brain v1 (common.py, embed.py, mcp_server.py)
  • Serves: Job A

1. Background and problem statement

The current MCP server exposes search_vault(query, top_k) — pure cosine similarity over Ollama embeddings. Three limitations:

  1. No structured filtering. The vault index already contains metadata (path, folder, tags, frontmatter, modified date), but none of it is queryable through the tool. A query like “homelab notes from the last quarter about upgrades” can only be approximated semantically.
  2. The agent is blind to the vault’s shape. Claude doesn’t know which folders, tags, or frontmatter keys exist, so it cannot construct precise queries and instead over-relies on semantic phrasing.
  3. Results are thin. Wikilinks-only results force one follow-up file read per hit, multiplying round trips and context cost.

Additionally, there is no way to scope a server instance to a subset of the vault. The privacy note in the README (hook context is sent to Anthropic) applies equally to MCP results; a structural scope is the correct fix, not score thresholds.

2. Goals and non-goals

Goals

  • G1. Single-call hybrid retrieval: metadata filters applied before semantic ranking.
  • G2. Schema-aware tool descriptions generated from the live index at server startup.
  • G3. Result payloads rich enough that one search call usually suffices.
  • G4. Per-instance scoping (path filter + custom instructions), composable with the existing multi-vault --db/--name mechanism.
  • G5. Zero new infrastructure at Layer 1 — SQLite + Ollama only. pgvector (Layer 2) remains a drop-in backend swap, not a requirement.

Non-goals

  • NG1. No write operations. The server stays read-only by design.
  • NG2. No changes to embed.py’s embedding model or chunking strategy (separate workstream).
  • NG3. No changes to Mode 1 (hook) behavior in this phase, except where shared code in common.py benefits it for free.
  • NG4. No reranker/LLM-in-the-loop retrieval. Cosine + filters only.

3. Current state (v1 contract, to be preserved)

  • Tools: search_vault(query, top_k=5) → wikilinks; get_vault_stats() → usable coverage report.
  • Config via env: SECOND_BRAIN_OLLAMA_URL, SECOND_BRAIN_EMBED_MODEL, SECOND_BRAIN_DB.
  • Embeddings keyed by path; search_vault inner-joins to files, silently dropping stale rows; embed.py prunes orphans.
  • Tests run without Ollama and without the mcp package installed.

Compatibility requirement: v1 calls (search_vault with only query/top_k) must behave identically in v2.

4. Functional requirements

5. Non-functional requirements

6. Interface specification

search_vault(
  query: str | None = None,
  top_k: int = 5,
  folder: str | None = None,
  tags: list[str] | None = None,
  modified_after: str | None = None,    # ISO 8601, vs. content_mtime
  modified_before: str | None = None,   # ISO 8601, vs. content_mtime
) -> list[Result]

Result = {
  wikilink: str, score: float | None,   # None for filters-only calls
  folder: str, tags: list[str],
  modified: str, snippet: str,
}

read_note(target: str) -> str            # wikilink or vault-relative path
get_vault_stats() -> str                 # + active scope, scoped counts

New environment variables (defaults preserve v1 behavior):

VariableDefaultPurpose
SECOND_BRAIN_SNIPPET_CHARS400Fallback snippet length
SECOND_BRAIN_SCHEMA_TAGS30Tags listed in schema block
SECOND_BRAIN_RESULT_BUDGET4000Approx. token cap per response

7. Implementation phases

Waterfall-friendly ordering; each phase is releasable.

  1. Phase 0 — vault-indexer prerequisites: tags table, scan-time writes, migration + backfill (FR-0x). Lands in the vault-indexer repo/folder with its own tests; second-brain v2 declares a minimum index schema version.
  2. Phase 1 — Query core: filter → rank pipeline in common.py (pure function over the DB, no MCP dependency), including FR-12/13/14 semantics. This is where most tests live.
  3. Phase 2 — Tool surface: extend mcp_server.py signatures, add read_note, result payload shaping and budget.
  4. Phase 3 — Schema generation: startup schema block + failure fallback (FR-2x).
  5. Phase 4 — Scoping: --scope/--instructions, register.py support, scoped stats and schema (FR-4x, NFR-5).
  6. Phase 5 — Docs: README update (tool table, hook-vs-MCP table gains a “filters” row), CHANGELOG entry, multi-vault guide update.

8. Testing requirements

Extend the existing suite; same constraints (no Ollama, no mcp package, in-memory SQLite fixtures).

9. Acceptance criteria

10. Risks and open questions

11. Appendix — content conventions that make retrieval work