mcp: project-plan — Vault Semantic Search
This commit is contained in:
parent
a2d5331738
commit
85130079cb
226
Sources/Homelab/vault-semantic-search.md
Normal file
226
Sources/Homelab/vault-semantic-search.md
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
---
|
||||||
|
created: '2026-05-17'
|
||||||
|
path: Sources/Homelab
|
||||||
|
project: vault-semantic-search
|
||||||
|
tags:
|
||||||
|
- mcp
|
||||||
|
- pgvector
|
||||||
|
- embeddings
|
||||||
|
- wiki
|
||||||
|
type: project-plan
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vault Semantic Search
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Add a `vault:search` MCP tool that takes a query string (plus optional
|
||||||
|
metadata filters) and returns vault notes ranked by semantic similarity.
|
||||||
|
The tool runs entirely against the `wiki` schema and reuses the embedding
|
||||||
|
infrastructure built by the Postgres consolidation plan.
|
||||||
|
|
||||||
|
Concrete use case: "search the vault for context" returns relevant notes
|
||||||
|
without needing to remember slugs or tag names. Semantic search replaces
|
||||||
|
the current "list everything, filter client-side" pattern.
|
||||||
|
|
||||||
|
This plan is scoped to vault search only. A future unified memory search
|
||||||
|
spanning vault + thoughts + reflections is anticipated via a `scope`
|
||||||
|
parameter, but is deferred until vault search is in use and shaping
|
||||||
|
requirements are clearer.
|
||||||
|
|
||||||
|
## Locked Decisions
|
||||||
|
|
||||||
|
### Tool surface
|
||||||
|
|
||||||
|
- Name: `vault:search`
|
||||||
|
- Read-only. No writes, no side effects.
|
||||||
|
- Single MCP tool — no separate "by tag" or "by date" variants. All
|
||||||
|
filtering is parameterized on this one tool.
|
||||||
|
|
||||||
|
### Inputs
|
||||||
|
|
||||||
|
- `query` (required, string) — natural-language search query
|
||||||
|
- `tags` (optional, list of strings) — restrict to notes carrying any of
|
||||||
|
these tags
|
||||||
|
- `domain` (optional, string) — one of the schema's domain values
|
||||||
|
(homelab, dev, venture, reference)
|
||||||
|
- `status` (optional, list of strings) — one or more lifecycle statuses
|
||||||
|
- `note_type` (optional, string) — restrict to project-plan,
|
||||||
|
session-notes, entity, topic-landing, or synthesis
|
||||||
|
- `date_range` (optional, object with `start` and `end`) — filter by
|
||||||
|
`updated_at` timestamp range
|
||||||
|
- `limit` (optional, int, default 10) — max results to return
|
||||||
|
|
||||||
|
Filters compose via AND. Vector similarity ranks within the filtered
|
||||||
|
candidate set.
|
||||||
|
|
||||||
|
### Output shape (minimal)
|
||||||
|
|
||||||
|
```
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"slug": "ob1-deployment",
|
||||||
|
"title": "OB1 Main Deployment",
|
||||||
|
"path": "Sources/Homelab/ob1-deployment.md",
|
||||||
|
"score": 0.82
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Score is the cosine similarity (or pgvector distance converted to
|
||||||
|
similarity — implementation detail). Caller uses `vault:get_project`
|
||||||
|
to fetch full content for any result they want to read.
|
||||||
|
|
||||||
|
Keeping content fetch separate from search keeps the search response
|
||||||
|
cheap and lets callers decide when full body content is worth the round
|
||||||
|
trip.
|
||||||
|
|
||||||
|
### Embedding behavior
|
||||||
|
|
||||||
|
- Query embedding runs **server-side** inside the vault MCP server.
|
||||||
|
- The server uses the **same embedding model** that produced the stored
|
||||||
|
vectors in `public.embeddings` (recorded in the `embedding_model`
|
||||||
|
column).
|
||||||
|
- Mismatches are not silently tolerated — if the configured query model
|
||||||
|
doesn't match the model recorded on the rows being searched, the
|
||||||
|
search returns an error rather than incorrect rankings.
|
||||||
|
- Embedding model selection is inherited from the consolidation plan;
|
||||||
|
this plan doesn't pick or change models.
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
|
||||||
|
- Searches the `wiki` schema only. Both Sources and Wiki layers are
|
||||||
|
searchable; filter via `note_type` if a caller wants Sources-only or
|
||||||
|
Wiki-only.
|
||||||
|
- Does not search `ob1.thoughts` or `ob1.reflections`. That's a separate
|
||||||
|
tool (or this tool extended with a `scope` parameter) in a future plan.
|
||||||
|
|
||||||
|
### Backend mechanics
|
||||||
|
|
||||||
|
- Vector similarity query against `public.embeddings` filtered by
|
||||||
|
`source_schema = 'wiki'`.
|
||||||
|
- Filters applied via WHERE clauses joining `public.embeddings` to
|
||||||
|
`wiki.notes` (or whatever the canonical wiki content table is named).
|
||||||
|
- HNSW index on `public.embeddings.embedding` is the consolidation
|
||||||
|
plan's responsibility; this plan reuses it.
|
||||||
|
|
||||||
|
## Open Items
|
||||||
|
|
||||||
|
- [ ] Final SQL query shape — JOIN pattern between `public.embeddings`
|
||||||
|
and the wiki content table depends on exact column names from the
|
||||||
|
consolidation plan. Resolve at implementation time.
|
||||||
|
- [ ] How to surface "no results" vs "search failed" — empty array
|
||||||
|
versus error response. Likely empty array for "no results" and error
|
||||||
|
for "model mismatch / query embed failed."
|
||||||
|
- [ ] Pagination — `limit` is in scope; offset / cursor pagination is
|
||||||
|
not. Add later if it's needed.
|
||||||
|
- [ ] Caching — query embedding cost is small but non-zero. No caching
|
||||||
|
in v1. If usage patterns show repeated queries, add later.
|
||||||
|
|
||||||
|
## Phases
|
||||||
|
|
||||||
|
### Phase 0 — Verify dependencies
|
||||||
|
|
||||||
|
- [ ] Consolidation plan's Phase 5 (vault indexing) is complete.
|
||||||
|
`public.embeddings` has rows where `source_schema = 'wiki'` covering
|
||||||
|
the full vault corpus.
|
||||||
|
- [ ] HNSW index exists on `public.embeddings.embedding`.
|
||||||
|
- [ ] Embedding model is documented and reachable from the vault MCP
|
||||||
|
server's host.
|
||||||
|
- [ ] The vault MCP user has read access to `public.embeddings`.
|
||||||
|
|
||||||
|
### Phase 1 — Tool implementation
|
||||||
|
|
||||||
|
- [ ] Add `search` function to the vault MCP server.
|
||||||
|
- [ ] Wire to the configured embedding model for query embedding.
|
||||||
|
- [ ] Build the parameterized SQL query (vector search + filters).
|
||||||
|
- [ ] Define the response shape per the locked decisions.
|
||||||
|
- [ ] Add input validation (filter values exist in schema, date format
|
||||||
|
correct, etc.).
|
||||||
|
|
||||||
|
### Phase 2 — Verification
|
||||||
|
|
||||||
|
- [ ] Smoke test: search for a topic that's definitely in the vault
|
||||||
|
("MCP", "Traefik") — confirm relevant results appear in top 5.
|
||||||
|
- [ ] Negative test: search for nonsense gibberish — confirm low scores
|
||||||
|
or empty results, not garbage matches.
|
||||||
|
- [ ] Filter test: search with `tags=["mcp"]` — confirm results are
|
||||||
|
restricted correctly.
|
||||||
|
- [ ] Combined filter test: query + tag + date range — confirm AND
|
||||||
|
semantics work.
|
||||||
|
- [ ] Model-mismatch test: temporarily configure a different embedding
|
||||||
|
model, confirm tool errors rather than returning bad rankings.
|
||||||
|
|
||||||
|
### Phase 3 — Documentation
|
||||||
|
|
||||||
|
- [ ] Add usage examples to the vault MCP server's README.
|
||||||
|
- [ ] Note the tool in CLAUDE.md so agents know to use it.
|
||||||
|
- [ ] Capture lessons learned in a session note.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
### Why this isn't part of the consolidation plan
|
||||||
|
|
||||||
|
The consolidation plan builds the substrate — embeddings table, vault
|
||||||
|
indexing, HNSW index. This plan builds the user-facing tool that
|
||||||
|
consumes the substrate. Splitting them keeps the consolidation plan
|
||||||
|
focused on infrastructure and lets the search tool ship with its own
|
||||||
|
small, focused scope.
|
||||||
|
|
||||||
|
### Why minimal output instead of full content
|
||||||
|
|
||||||
|
Most searches are exploratory — "what do I have on X" — and only a
|
||||||
|
fraction of returned hits will actually be read. Returning bodies in
|
||||||
|
the search response means every search pays for content fetching that
|
||||||
|
mostly isn't used. Keeping search lightweight and giving callers a
|
||||||
|
separate `get_project` path matches the actual usage pattern.
|
||||||
|
|
||||||
|
### Why a single tool with parameters instead of separate tools
|
||||||
|
|
||||||
|
A single parameterized tool (`vault:search` with optional tags, domain,
|
||||||
|
etc.) is more flexible than many tools (`vault:search_by_tag`,
|
||||||
|
`vault:search_recent`, etc.) and matches how natural-language queries
|
||||||
|
combine criteria. "Find recent context-window notes tagged claude-code"
|
||||||
|
is one tool call with three filters, not three tool calls composed
|
||||||
|
manually.
|
||||||
|
|
||||||
|
### Why server-side embedding is the only correct choice
|
||||||
|
|
||||||
|
Each embedding model produces vectors in its own geometry. Vectors from
|
||||||
|
different models aren't comparable, and a model mismatch produces
|
||||||
|
silently wrong rankings — not an error, just bad results. The server
|
||||||
|
owns the stored vectors and knows which model produced them, so it's
|
||||||
|
the right place to ensure the query uses the same model. Letting
|
||||||
|
clients embed queries themselves would require every client to know
|
||||||
|
which model is correct and would fail invisibly when wrong.
|
||||||
|
|
||||||
|
### Future extension: scope parameter
|
||||||
|
|
||||||
|
Anticipated shape for the unified memory search that follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
vault:search(query, scope="vault" | "memory" | "all", ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `scope="vault"` — current behavior, wiki schema only (default)
|
||||||
|
- `scope="memory"` — ob1.thoughts and ob1.reflections only
|
||||||
|
- `scope="all"` — everything
|
||||||
|
|
||||||
|
Implementation extends the WHERE clause to include additional
|
||||||
|
`source_schema` values. The output shape grows a `source` field so
|
||||||
|
callers can tell vault hits from memory hits. Deferred to a separate
|
||||||
|
plan once real vault-search usage informs the design.
|
||||||
|
|
||||||
|
### Flag for Lovebug
|
||||||
|
|
||||||
|
The Postgres consolidation plan (`postgres-consolidation-reflection-layer`,
|
||||||
|
saved 2026-05-15) was written via the now-deprecated `save_note` path.
|
||||||
|
The vault markdown file exists at
|
||||||
|
`Sources/Homelab/postgres-consolidation-ob1-reflection-layer.md`, but
|
||||||
|
the Postgres row does not — `vault:get_project` returns "not found" for
|
||||||
|
that slug. The plan needs to be re-registered via `vault:create_artifact`
|
||||||
|
so it shows up in `list_projects`, can have its status updated, and
|
||||||
|
participates in the Trello card projection. Not blocking for this
|
||||||
|
search plan, but worth handling before the consolidation plan goes
|
||||||
|
into execution tracking.
|
||||||
Loading…
Reference in New Issue
Block a user