A retrieval-augmented generation pipeline that answers questions about my meetings. Wispr Flow transcribes every call I take and writes the transcript into a Notion database. This system pulls those pages from Notion, embeds them into a local vector store, and answers questions from a terminal chat loop using only the excerpts it retrieved. 118 meetings, one file, no infrastructure.
Notion's search matches keywords. It finds the meeting where someone typed the exact phrase and misses the one where the same decision was reached in different words. Questions like “what did we agree on with Qobuz in the last workshop?” are not keyword questions. They are questions about what a conversation concluded.
The deeper problem was what I was actually searching. These are transcripts, not notes. A written note is already a summary: someone decided what mattered and wrote that down. A transcript is forty minutes of unedited speech. It contains filler, false starts, three unrelated topics in one document, and the one sentence that records a real decision buried mid-paragraph in language that never restates what the meeting was about.
That distinction shaped most of what follows. Standard RAG tutorials assume documents that are about something. A meeting transcript is about four things, none of them announced.
Build something I would use daily. That meant three things: answers grounded strictly in my transcripts, an honest “I don't know” when they don't cover it, and an index that keeps itself current as Wispr Flow adds meetings.
I wrote steps 3 and 4. Steps 1 and 2 already existed and cost me nothing per meeting, which is exactly why three years of transcripts had accumulated without me ever reading them back.
That was obviously wrong. There is always a newest meeting. I spent a while treating it as a tuning problem, adjusting k and the rerank floor, before recognizing it as a hard limit of the approach.
That question is ORDER BY date DESC LIMIT 1 over metadata. Embeddings cannot express it. Retrieval latched onto the literal word “meeting” and returned chunks from pages that Wispr Flow had auto-titled “Untitled meeting”, while the genuinely newest transcript was never in the candidate set at all. Reranking could not rescue it, because reranking only reorders what retrieval already found.
So these questions skip retrieval entirely. When the scope analyzer sets an aggregate mode, a separate path scrolls page metadata, deduplicates chunks back into meetings, sorts by timestamp, and builds the context directly.
Two details made this work. The computed context is labelled as a calendar summary computed directly from the index, so the model reads it as fact rather than mistaking it for a transcript excerpt and refusing to answer from it. And meetings are keyed on the Notion page ID rather than the title, because auto-captured titles repeat heavily: 16 of my 118 meetings are titled “BCE Sync”, another 5 are “BCE SYNC”, and two share both a title and a date. Keying on title would have merged them into one meeting.
The classification rides along on the scope call that already runs for every question, so it costs no extra latency.
I also drew a boundary and left it there. “How many meetings did we have with Holovox” combines an aggregate with an entity, and “Holovox” has to be matched against transcript content, which is a semantic problem again. Answering that properly needs hybrid search. The metadata path handles dates only, and I documented that it does.
The interesting work in RAG was not the retrieval. It was noticing which questions retrieval is structurally wrong for, and building a different path for those instead of tuning parameters until the symptom moved.
Most of the rest came from taking the input seriously. Building over transcripts rather than notes is a different problem than the tutorials describe, and nearly every decision I would defend in this project traces back to that: retrieving wide because speech has poor precision, stamping the title on every chunk because dialogue rarely names its own subject, and writing a prompt that expects fragments rather than prose.
The recurring theme was making the system's uncertainty legible rather than hiding it. A reranker that can return nothing. A stale index that refuses to answer. A date filter that falls back instead of failing closed. Each of those is a place where the easy implementation produces a confident wrong answer and the honest one produces a visible gap. For a tool answering questions about my own work, the visible gap is worth more.
Those are the next things to fix, and hybrid search is what unlocks most of them.