Week 2Day 10 of 30120 minutes~61 min of reading

Build — the retrieve in retrieval

Embeddings and semantic search

Build enough to be dangerous

Why this matters for a delivery manager

Most 'chat with our documents' programs die in retrieval, not in the generator. If you cannot say 'we measured recall@5 on 40 real questions,' you do not have RAG — you have a demo. Today you learn the retrieve half so tomorrow's RAG conversation is not theatre.

You will be in rooms where someone wants to 'just embed the Confluence.' That sentence hides chunk size, overlap, metadata, ACLs, hybrid search, and an eval set. Those are product decisions with owners. If you do not take them, an engineer will pick 500-character chunks at midnight because the tutorial did, and you will spend a quarter explaining fluent wrong answers.

The practice is a 10-question retrieval eval for a project wiki. You will fold it into the day 14 artifact. Treat it as a real object, not a homework flourish. Hiring managers who have shipped this will ask how you built the question set. 'I used the demo script' is a fail.

You will be able to

  • Explain an embedding as a meaning-vector, not as 'AI search magic'
  • Describe cosine similarity and why keyword search still belongs in the design
  • Specify chunk size as a product decision, not an implementation leftover
  • Write an eval for retrieval: did we get the right page, not the pretty paragraph

2-hour clock

120:00

Now: Read vectors, chunks, hybrid search, and eval · 50m

The 2-hour session

Concepts, in full

This block is a slow read — about an hour with the diagrams. After each concept, write one sentence in notes (what you already do vs what is new) and tick annotated. Do not skim the last concept.

01

A vector is a point in meaning-space

The embedding model reads a chunk and emits a list of numbers — hundreds to a few thousand dimensions. You do not read the numbers. The geometry is the point: chunks about the same thing sit near each other even if they share few words. 'PTO' near 'annual leave.' 'Change freeze' near 'code cutoff.' That is the trick, and it is also the failure: the geometry is meaning-ish, not identity-ish. Policy POL-1044 is a name. Names want a keyword match.

At query time you embed the question with the same model, then ask a vector index for the nearest neighbors. Those chunks are what you stuff into the generator's context. If the neighbors are wrong, the answer is wrong with a straight face. This is why 'we will fix it in the prompt' is usually a stall. The generator cannot cite a page it was never given.

Same model on both sides. If you embed the corpus with model A and the query with model B, the points are not in the same space. Distances are junk. This happens when someone 'upgrades the embedder' and re-embeds queries only, or when two vendors' vectors get concatenated because a slide said 'ensemble.' Re-embed the corpus when you change the embedding model. Budget that job. It is not free, and it is not optional.

Cosine similarity is the usual distance: how aligned are the two arrows, ignoring how long they are. You do not need the formula. You need the implication: a score of 0.82 is not '82% true.' It is 'closer than 0.61 on this model, this corpus, this week.' Do not put raw cosine scores in the UI as if they were confidence. They are useful for ranking and for a threshold you will tune on an eval set. They are not a compliance control.

Meaning-space fails in predictable ways. Short queries. Identifiers. Numbers. Negation ('not in scope for contractors'). Tables. Code. Cross-language. Near-duplicate policies from two years. The embedding will still return something nearby, because that is its job. Nearby is not 'the clause that answers this.' Keyword search, filters, and structure-aware chunking exist because meaning-space is a tool, not a mind.

Do not use a chat model as your search index. Chat models generate text. Embedding models map text to vectors. Some vendors sell both. They are different products with different evals. A frontier chat model that 'can search' is usually calling a retriever behind the scenes or pretending. Ask which, and ask for the retrieval eval.

Cost and latency sit here too. Embedding the query is a model call on every question. Embedding the corpus is a batch job when documents change. Vector indexes (pgvector, vendor DBs, specialist stores) have their own ops: sizing, filters, recall vs speed. You do not have to pick the index today. You do have to refuse a design that has no owner for 'what happens when a page is updated.'

Meaning-space is a tool you can explain in a minute and misuse for a quarter. The minute: the embedder puts paraphrases near each other; the index returns neighbors; the generator only sees those neighbors. The quarter: someone embeds the whole tenant, skips keyword search, reports 'semantic search is live,' and then cannot find POL-1044. Your standing move is hybrid plus an eval that includes identifiers, empty-gold, and a stale trap. If a vendor demo only shows paraphrases of the happy-path SOP, ask them to look up an ID and a question that should retrieve nothing. The demo will wobble. That wobble is the work. Do not apologize for making it wobble. You are not being difficult. You are refusing to fund a meaning-shaped pile.

Diagram

Keyword space vs meaning-space

A

Keyword / BM25

  • Hits exact IDs, SKUs, error codes, policy numbers
  • Misses synonyms: PTO vs annual leave
  • Transparent: you can see the matched tokens
  • Cheap, well understood, filter-friendly
  • Fails on paraphrased questions
B

Vector / embedding

  • Hits paraphrases and synonyms
  • Misses identifiers and precise numbers
  • Opaque: a score is not an explanation
  • Needs the same embedder on query and corpus
  • Fails on negation, tables, 'retrieve nothing'

Grown-up retrieval uses both. Pure vector is a demo. Pure keyword misses the synonym. Hybrid is the default until an eval says otherwise.

02

Chunking is a product decision

Chunking is splitting documents into the units you retrieve. Too big: the vector is a mush of topics; you retrieve a 4,000-word blob, waste the context window, and bury the clause. Too small: you retrieve a sentence without the exception that lived in the next paragraph. Overlap (often 10–20%) is cheap insurance so a split does not cut a rule in half. These numbers are not a library default you shrug at. They are the product.

Respect document structure before you respect character counts. Headings, SOP sections, numbered clauses, ticket comments, decision-log entries — those are already units a human would retrieve. A delivery lead can demand 'chunk by heading, not by raw 500 characters' in a design review. That one sentence saves weeks of fluent nonsense. If the corpus has no structure, the first workstream is to give it some, not to embed the mess harder.

Token size is a budget. 400–800 tokens per chunk is a common starting band for policy prose, with ~80–120 tokens of overlap. Tables may want the whole table plus the heading that explains it, even if that blows the band. Code and API docs want function-level or endpoint-level units. Meeting notes want 'decision' blocks if you can detect them, otherwise they should perhaps not be in the index at all. One size is how junk gets a vector.

Overlap is not free. It duplicates text in the index, duplicates cost at embed time, and can dominate the top-k with near-copies of the same paragraph. 10–20% is a starting point, not a religion. If recall is fine and the top 5 are three copies of the same heading, you over-overlapped or you failed to de-duplicate. Measure.

Metadata is not optional: source path, heading trail, last-updated, document type, product, region, ACL / permission group. You will filter on these. 'Similar' but from the wrong country is a wrong answer. 'Similar' but from the 2019 SOP that was never archived is a wrong answer with legal flavor. Store the metadata at ingest. You cannot reconstruct ACL from the embedding later.

Drop junk at ingest or you will retrieve it forever: nav chrome, cookie banners, 'print this page,' 'click here,' duplicated sidebars, scanned PDFs with no OCR, Excel dumps with 40 empty columns. Demand a sample of 20 chunks on a slide before you fund the rest of ingest. If the sample is garbage, the index will be a garbage-shaped meaning-space. The generator will quote the cookie banner with a straight face.

Re-embed on document change, not on a nightly prayer. Event-driven when you can. A documented lag ('answers may be 24h stale') shown in the UI when you cannot. Silent staleness is an incident class of its own — tomorrow's day, but the hook is here: chunking without an update story is a one-shot demo.

Write chunk size, overlap, and structure rules as product copy, not as a library default. Example you can steal: split on H2; max 800 tokens; 100-token overlap; keep tables with their heading; drop nav and cookie banners; store path, heading trail, date, permission group; re-embed on change. Then demand a sample of 20 chunks on a slide before ingest is funded. If the sample is junk, you do not have a chunking spec — you have a tutorial. Overlap is insurance, not a religion; if the top 5 are three copies of the same heading, you over-overlapped or failed to de-duplicate. A delivery lead who will not say 'chunk by heading' in a design review will spend weeks explaining fluent nonsense. Say it. Put it in the ticket. Make changing it an eval, not a notebook experiment somebody ran on Sunday.

Diagram

From document to index row

01

Clean

Drop nav, cookies, boilerplate. OCR if you must. Refuse scans you cannot read.

02

Split on structure

H2 / SOP section / decision entry first. Fall back to a token cap only inside a unit.

03

Overlap + cap

e.g. max 800 tokens, 100-token overlap. Do not cut tables in half.

04

Attach metadata

source, heading trail, date, product, region, permission group.

05

Embed + upsert

Same embedding model you will use at query time. Re-embed on change, not by hope.

Each arrow has an owner. 'The AI team' on every arrow is a failed RACI. Chunk size and overlap are decided before anyone calls an embed API.

03

Hybrid search is the grown-up default

Pure vectors miss exact identifiers: policy POL-1044, SKU, error code, RFC number, ticket id. Keyword / BM25 search still wins on those. Hybrid means retrieve from both, merge, maybe rerank. Filters (region = EMEA, product = Atlas, audience = PM) happen as a hard constraint — before or after the similarity search, in the index — not as a hope in the prompt. 'Please only use EMEA sources' in the system prompt is not a filter. It is a wish.

Merging two lists is a product choice. Simple: take the union of top k_keyword and top k_vector, dedupe by chunk id, keep a combined score. Slightly less simple: reciprocal rank fusion, which does not require the scores to be on the same scale. Do not start by training a mixer. Start with union + filters, measure recall@k, then decide if you need a reranker.

Rerankers are a second model that scores (query, chunk) pairs more carefully on a shortlist. Use when recall is acceptable (the right chunk is in the top 20) but the top 3 are messy. Do not start there. A reranker will not find a chunk the first stage never retrieved. It also adds latency and cost on every query. Put it behind a measured need, not behind a vendor slide.

Filters are how you keep retrieval from being a clever intern with a master key. Date windows, product, region, document type, 'current vs archived,' permission group. The query 'what is the travel cap' should not retrieve a 2014 blog post because it used similar words. Default filter: not archived, in the user's ACL, in the in-scope spaces. Make the defaults visible in the UI ('searching Atlas SOPs, current only') so operators can see the cage.

When hybrid still fails: the document was never ingested, the identifier is in an image, the answer is in a table the chunker smashed, the question is about a live number in SAP. Those are not ranking problems. They are corpus, ingest, or 'this should be a tool call' problems. Retrieval eval will show them as systematic misses. Treat systematic misses as backlog with an owner, not as 'we need a better embedder.'

Search UX belongs in the design even if v1 is a chat box. Show the chunks. Let a user open the source. Let them see that a keyword hit on POL-1044 is why that chunk appeared. Transparency is how you debug and how you earn trust. A chat box that hides retrieval is a magician's hat. Magicians' hats do not pass audit.

Write hybrid as the default in the design unless an eval on your corpus says keyword-only or vector-only is enough. 'Enough' means recall@k on real questions, including the identifier questions. If your eval set has no identifier questions, you cooked the exam.

Hybrid is the default until an eval on your corpus says otherwise. Pure vector is a demo. Pure keyword misses the synonym. Filters are hard constraints in the index, not wishes in the prompt. 'Please only use EMEA sources' is not a filter. region = EMEA on the query is a filter. Rerankers come after recall is acceptable on a shortlist; they will not find a chunk the first stage never retrieved. Show the chunks in the UI so operators can see why a hit appeared. A chat box that hides retrieval is a magician's hat, and magicians' hats do not pass audit. Write the merge rule (union of top k from each side, or reciprocal rank fusion) in the design so two engineers do not invent two mixers. Start simple. Measure. Then spend.

04

Retrieval eval: recall is the adult metric

Build 30–80 real questions with the business for a live system. For a design pack, 10 well-chosen questions is the floor, 20 is better. For each, a human marks the document and section that must be retrieved. Measure recall@k: in the top k chunks, was the needed one there? If recall@5 is 40%, stop tuning the prompt. Fix retrieve. The generator cannot save you.

This eval is yours to own as a delivery lead. Engineers will build the index. You will notice if the test questions are all happy-path from the demo script. Sit with three actual users and harvest questions from their last week of Slack, tickets, or stand-up. Include the ugly ones. Include one that should retrieve nothing. Include one whose answer lives in a table. Include an identifier. If the set is only paraphrases of 'what is our PTO policy,' you built a marketing brochure.

Precision@k is useful later (how much junk came with the gold). Start with recall. Missing the needed chunk is a hard fail for RAG. Extra junk is a cost and a distraction the generator might ignore. You will care about precision when you are stuffing 20 chunks 'to be safe' and blowing the window. First, find out whether the gold is even in the pile.

Gold labels must be specific. Not 'the handbook.' 'SOP-14.pdf, heading 4.2 Travel — contractors, 2024-03-12 version.' If two documents could answer, list both as acceptable. If the question is underspecified, fix the question. An eval row with a vague gold is how you argue about scores instead of fixing retrieve.

A 'should retrieve nothing' question is a first-class row. Gold: empty. Success: the system returns no chunk above threshold, or returns chunks the generator is instructed to treat as irrelevant, and the product refuses. Failure: it retrieves a vaguely related SOP and answers anyway. This row is how you catch the 'always returns something' disease of vector search.

Do not let the builder write all the questions. Do not let the sponsor write only the questions they will demo. Mix. Version the eval set like a document: owner, date, source of questions, known holes. When the corpus changes, some gold labels will rot. Re-judging is a job with a clock, not a hope.

Report recall@5 (and maybe recall@10) as a number with a denominator, not as 'pretty good.' 22/40 at k=5 is a conversation. 'The search feels smart' is not. Put the number on the steering slide. Put the three systematic misses next to it. That is how you get time to fix chunking instead of time to 'try GPT-next.'

Recall@k is the adult metric because the generator cannot cite a page it was never given. Report it as a fraction with a denominator, not as 'pretty good.' Gold labels are specific (file + heading + version), the question mix includes identifiers, tables, stale traps, and a should-retrieve-nothing row, and the set is harvested from real users rather than the demo script. Precision can wait until you are stuffing 20 chunks out of anxiety. First find out whether the gold is in the pile. This eval is yours. Engineers will build the index. You will notice if the questions are too kind. Sit with three actual users. The set will hurt. Good. A kind eval is how you get 90 percent on the slide and 40 percent on Monday.

A 10-question retrieval eval — shape, not a take-home of made-up trivia. Steal the mix; write your own rows for the wiki you pick on day 14.
#Question typeExample askGold you must retrievePass rule
1Exact IDWhat does POL-1044 say about contractors?POL-1044 current PDF, not the 2019 copyID'd doc in top 5
2How-do-weHow do we request a production change freeze?SOP Change Management, H2 Requesting a freezeSection in top 5
3Decision logWhat did we decide about vendor X in Q2?Decision log 2024-06-12 vendor XThat entry, not a rumor
4TableWhat is the travel cap for band 4?Rates table in Travel SOP, current FYTable chunk intact
5SynonymWhat's the annual leave carryover?PTO policy carryover clauseVector should help
6Region filterEMEA retention period for logs?EMEA records SOP, not USFilter must hold
7Stale trapCurrent password rotation policy?2025 IAM SOP, not 2021 wikiLatest wins
8OwnerWho owns the Atlas RAID process?RACI in Atlas space, named roleName + source
9Should be emptyWhat is Jane's salary band?Nothing in-scope; HR is outRetrieve nothing / refuse
10Multi-hop-ishDoes the Q2 vendor X decision conflict with current SOP?Decision log AND current SOPBoth in top k, or two-step retrieve

A 10-question retrieval eval — shape, not a take-home of made-up trivia. Steal the mix; write your own rows for the wiki you pick on day 14.

05

Chunk size, overlap, and k are product knobs with costs

k is how many chunks you retrieve. 5 is a common chat default. 10 if you will rerank down to 3–5. 20 is how you blow the context window and your bill. k is not 'more is safer.' More is noisier and more expensive. Set k from the eval: the smallest k at which recall is acceptable. Then generate from that. Stuffing 20 because recall@5 was 40% is treating a retrieval problem as a context-window problem.

Chunk size trades recall of a clause against purity of the vector. Larger chunks help when the answer needs surrounding exceptions. Smaller chunks help when documents mix topics. You find out with the eval, not with a Twitter default. Change one knob at a time. If you change size, overlap, k, and the embedder in one weekend, you will not know what helped.

Overlap trades boundary safety against duplication. If your splitter respects headings, you may need less overlap. If you split on raw tokens through unstructured dumps, you need more, and you should also ask why you are indexing unstructured dumps. Overlap will not fix a corpus that should have been edited by a knowledge owner.

Thresholds: some designs drop chunks below a similarity score. Dangerous until you have calibrated on your eval. A threshold that feels high will kill identifier questions that hybrid would have saved. If you threshold, do it after hybrid merge, and measure the empty-retrieval rate on the 'should retrieve' rows. A threshold is a recall knife.

These knobs belong in the design pack with owners and a re-tune trigger: 'we retune if recall@5 on the frozen eval drops below X, or when we change embedder, or when the corpus mix shifts (lots of tables, new space).' They do not belong as comments in a notebook.

Cost envelope: embedding the corpus is tokens × documents × recency. Query embedding is tokens × questions. Index storage is vectors × dimensions × copies (overlap). Generation cost is usually larger than retrieve cost at chat volumes, but re-embedding a giant corpus on every embedder upgrade is a real invoice. Put a back-of-envelope in the design. Day 5 math again.

When a sponsor says 'just embed everything,' your answer is this section: we will chunk by structure, store metadata and ACLs, hybrid-search, and we will not spend the ingest budget until 20 sample chunks and 10 eval questions exist. That is not process for its own sake. That is how you avoid a pretty index of junk.

Treat k, chunk size, overlap, and similarity thresholds as knobs with owners and a re-tune trigger. Change one at a time. The smallest k at which recall is acceptable is the k you generate from; stuffing 20 because recall@5 was 40 percent treats a retrieval problem as a context-window problem and will not save you. Thresholds are recall knives — do not cut until you have calibrated on the eval, and never threshold away hybrid's identifier hits. Cost sits here: corpus embed, query embed, storage of overlapped vectors, and the invoice when you change embedder and must re-embed everything. Put a back-of-envelope in the design. When a sponsor says 'just embed everything,' your answer is this section, out loud, without apology.

06

A 10-question retrieval eval you can defend

The practice today is not to invent a perfect wiki. It is to write 10 questions a new PM would actually ask, name the source that must come back, and define success as recall@5. You will reuse this table on day 14. Write it as if a skeptical engineer will try the questions against a real index in a week.

Harvest, do not brainstorm in a vacuum. If you have a real program wiki, skim the last month of questions in Slack, office hours, or onboarding notes. If you do not, invent a named program (Atlas is fine) with named artifacts: SOP-14, decision log, RAID, vendor contract, RACI. Specific fake is better than generic real. 'Corporate knowledge' is not a corpus.

Balance the mix using the table in c4. If you notice all 10 are how-do-we paraphrases, delete four and add an ID, a table, a should-be-empty, and a stale trap. The empty row is the one sponsors will want to drop because it 'isn't a demo.' It is the row that saves you from answering HR questions out of a project space.

Define failure as well as success. Success: gold source in top 5. Failure: gold missing, or (for the empty row) any in-scope answer generated. A separate failure: gold present but the generator ignored it — that is tomorrow's groundedness eval, not today's retrieval eval. Keep the two numbers apart. They fail independently and they have different owners.

Write two chunking rules you would demand before anyone embeds a byte. Example: split on H2; max 800 tokens with 100 overlap; keep tables whole with their heading; drop anything in /archive; store permission group. Rules beat adjectives ('chunk them reasonably').

Name the knowledge owner on the eval itself. If you cannot name who will re-judge gold labels when SOPs move, the eval will rot and you will be steering from a dead number. Rotten evals are worse than no evals because they provide cover.

Stop when the table is ugly and specific. Pretty questions are a smell. 'Explain our culture of quality' is not a retrieval question. 'Where is the definition of done for Atlas UAT' is. You are building a test set, not a keynote.

The 10-question table is a design object you will reuse on day 14, not homework. Harvest from Slack and onboarding, invent a named program if you must, and keep the mix honest. Write two chunking rules before anyone embeds a byte. Name the knowledge owner who will re-judge gold when SOPs move. Define failure separately from success: gold missing is a retrieve fail; gold present but ignored is a groundedness fail tomorrow. Do not blend them into one 'accuracy' number a vendor can hide behind. Stop when the table is ugly and specific. Pretty questions are a smell. If you would not ask it on Monday as a new PM, delete the row and write one you would.

Eval row shape — JSONL, one question per line, gold is a source id not a vibe
{"id": "q01", "type": "exact_id", "q": "What does POL-1044 say about contractors?",
 "gold_source": "POL-1044.pdf#contractors", "k": 5, "must_retrieve": true}
{"id": "q09", "type": "empty", "q": "What is Jane's salary band?",
 "gold_source": null, "k": 5, "must_retrieve": false}

# scoring a run (sketch)
# recall_at_k = hits / n_must_retrieve
# empty_ok    = rows where must_retrieve is false and retrieved == []
# do not mix these into one 'accuracy' number

Worked case · stay here ~20 minutes

The chunking argument

Friday design review, Atlas wiki copilot. Priya has a notebook that embeds the Confluence export in 500-character chunks with 50 of overlap, copied from a vendor tutorial. You have a one-page chunking spec: split on H2, max 800 tokens, 100-token overlap, tables kept whole. Marcus is in the room and wants to just start embedding. You have twenty minutes.

Priya shares the notebook. Cell three is the splitter: RecursiveCharacterTextSplitter, chunk_size=500, chunk_overlap=50, copied from a tutorial that used blog posts. She has already embedded 1,200 chunks of the Atlas export on a personal key. The slide says semantic search is live. Marcus is pleased. You are not here to be pleased. You ask to see twenty chunks, not the notebook, not the cosine scores. She pastes a sample. Chunk four is the second half of a table of travel rates, no heading, no year. Chunk seven is Skip to content plus a cookie banner. Chunk twelve is a sentence that ends mid-clause, and the exception that belongs to it is in chunk thirteen, which did not make the top-k in her demo query. That sample is the design review. The notebook is a tutorial with a bill. You say so without adjectives: the sample is junk, we do not fund the rest of ingest, and 500 characters is not a product decision, it is a library default. Marcus hears delay. Let him. A pretty index of junk is how you spend a quarter explaining fluent nonsense.

Priya's argument is not stupid. Short chunks make purer vectors. The tutorial said 500. Overlap of 50 is 10 percent, which is in the band. She can point at a recall number from five paraphrases of the PTO policy, 5/5 at k=5. That number is a cooked exam. You put the day-10 mix on the table: an exact ID (POL-1044), a how-do-we, a decision-log row, a table (travel cap for band 4), a synonym, a stale trap, a should-retrieve-nothing. You have not harvested all ten yet. You have four, including the table and POL-1044. You ask her to run those four against the 500-character index, in the room. POL-1044 is a miss. The table is a miss; the rates were split and the vector is a mush of two bands. The PTO paraphrase still hits. That is the whole argument in one run. Meaning-space finds synonyms. Meaning-space smears identifiers and tables. Hybrid and structure-aware chunking exist because of that smear. A tutorial that never looked up an ID is not an eval. Run the mix or you are demoing.

You write two rules on the whiteboard while the miss is still on the screen. Split on H2 headings first; fall back to a token cap only inside a unit. Max 800 tokens, 100-token overlap. Keep tables with their heading, even if that blows the band. Drop nav, cookies, print-this-page. Store path, heading trail, last-updated, permission group. Re-embed on change, not on a nightly prayer. Priya says 800 is too big, the vector will be a mush of topics. Sometimes it will. Atlas SOPs are already sectioned by humans. A human-sized unit is the retrieval unit until an eval says otherwise. If a heading contains two topics, that is a knowledge-architecture problem, not a reason to slice every 500 characters through the heading. You offer the experiment she actually wants: freeze the four questions plus six more by Monday, run 500-char versus H2-800 on the same embedder, report recall at 5 as a fraction. Change one knob. If 500 wins on the mixed set, you will eat the spec. You do not think it will win. You are willing to be wrong in public.

Overlap is the next skirmish. She wants more overlap to be safe now that you have scared her about split clauses. Overlap is insurance, not a religion. It duplicates text, duplicates embed cost, and can dominate the top-5 with three copies of the same heading. If recall is fine and the top-5 are near-copies, you over-overlapped or you failed to de-duplicate. Atlas pages are duplicated across a 2023 space and a 2024 space already. Her 1,200 chunks include both. k is being eaten by twins. You add de-dupe by canonical URL or content hash to the ingest list, before anyone tunes overlap. Marcus asks whether this is blocking the CEO demo. You say: the sample of twenty chunks is a gate. If the sample is a cookie banner and a split table, ingest is not funded. The CEO demo on a garbage-shaped meaning-space is how you get a screenshot of Skip to content cited as policy. He has seen that movie in a vendor POC last year. He goes quiet. Use the quiet.

Metadata is not optional, and it is the part of the argument Priya treats as polish. Source path, heading trail, date, document type, product, region, permission group. You will filter on these. Similar but from the 2019 SOP is a wrong answer with legal flavor. Similar but from Orion's space is a wrong answer with an ACL flavor you will meet on day 12. You cannot reconstruct ACL from the embedding later. Store it at ingest. She says the tutorial's schema is text plus embedding. Of course it is. Tutorials do not have tenants. You are building in one. The query what is the travel cap should not retrieve a 2014 blog post because the words are close. Default filter: not archived, in the user's ACL, in Atlas. Make the cage visible in the UI later. Today, make it visible in the schema. A chunk without a permission group is a chunk you will regret. Write that as a RAID row in the same meeting: index stores text without ACL or date. Trigger: sample of 20 lacks those fields. Mitigation: schema before the next embed job.

Hybrid is how you stop the POL-1044 miss without throwing away vectors. Pure keyword would have hit the identifier and missed the synonym. Pure vector did the opposite. Retrieve from both, merge, maybe rerank later. Filters are hard constraints in the index, not please only use Atlas sources in the prompt. Priya wants to add a reranker this afternoon because a vendor slide had one. You refuse. A reranker will not find a chunk the first stage never retrieved. Recall is not acceptable on identifiers. First stage is the work. Union of top-k keyword and top-k vector, dedupe, measure. Reciprocal rank fusion if scores will not sit on one scale. Do not train a mixer. Start simple. Show the chunks in the UI so Marcus can see why a hit appeared. A chat box that hides retrieval is a magician's hat. Magicians' hats do not pass audit, and they do not help you debug this argument next week when someone claims the AI is dumb. The AI retrieved the cookie banner. That is a chunking miss, not a model miss. Fix the unit. Then talk models.

Junk at ingest is the cheapest win and the one engineers skip because it is not a model. Nav chrome, cookie banners, click-here, scanned PDFs with no OCR, Excel dumps, meeting notes with no decision. Drop or flag. If half the Atlas SOPs are pictures of pages, you do not have a RAG corpus. You have a scanning project. Name it. Do not hide it inside we will embed Confluence. Unreadable text still gets a vector. Neighbors of garbage are more garbage. Priya's sample already has the banner. That is enough to institute the gate. You demand the twenty-chunk slide before any further spend on the embedder. Knowledge owner — you look at Marcus — samples them. If he will not, the corpus is unowned, and unowned wikis do not become owned because you added vectors. They become higher-confidence unowned wikis. He names a PMO lead. Write the name. That is the first actual decision of the meeting. The splitter can wait for the name. It should have waited before cell three ran. A named owner beats a live index of junk. Always.

k, size, overlap, and thresholds are knobs with owners, not a notebook you re-run on Sunday. Set k from the eval: the smallest k at which recall is acceptable. Stuffing 20 because recall at 5 was 40 percent treats a retrieval problem as a context-window problem. Thresholds are recall knives; do not cut until you have calibrated, and never threshold away hybrid's identifier hits. Change one knob at a time. If you change size, overlap, k, and the embedder in one weekend, you will not know what helped, and you will ship a superstition. Cost sits here: corpus embed, query embed, storage of overlapped vectors, the invoice when you change embedder and must re-embed everything. Her 1,200 chunks at 500 characters with overlap are already a bill. Re-doing them under the H2 rule is a smaller bill than explaining the table miss to steering. Put a back-of-envelope on the page. When a sponsor says just embed everything, this section, out loud, is the answer. You just said it. Leave it on the board. Do not apologize for the envelope being ugly.

You settle the argument with a written experiment, not with seniority. Frozen set: ten questions, mix intact, gold as filename plus heading plus version, including POL-1044, the rates table, a stale trap, and a should-retrieve-nothing. Two chunkers, same embedder, same hybrid merge, k=5. Report recall at 5 as a fraction with a denominator, plus empty-ok on the salary row. Priya owns the runs. You own the questions and the gold. Knowledge owner re-judges if SOPs move. Results in three days, before anyone talks about a generator prompt. If H2-800 wins, the tutorial default is deleted, not commented out. If 500 wins on the mixed set, you change the spec and you say so in steering. Either way the notebook is not the source of truth. The eval is. Marcus wants a winner today. He cannot have one. He can have a gate and a date. That is more than he had when the slide said semantic search is live. Semantic search was live on a cookie banner. You killed that sentence. Good. Send the experiment before Monday.

What you do not do in the last five minutes: design the generator, pick a vector database brand, or accept we will fix it in the prompt. The generator cannot cite a page it was never given. Fluent wrong SOP is a retrieve miss until recall says otherwise. You also do not let Priya keep the 1,200-chunk index for now as a parallel. Parallel indexes become the production pattern because they already exist. Delete or quarantine. The next embed job uses the spec. Sample twenty. Then scale. This is not process for its own sake. This is how you avoid a pretty index of junk. You already knew that as a delivery lead on data migrations. Same muscle, new object. The object is a chunk. The unit is a heading. The test is recall at 5 on questions a new PM would actually ask. If Priya is still annoyed, that is information: she wanted a demo. You wanted a product. The meeting was to make that visible to Marcus. It is visible. Stop talking. Send the two rules and the ten-question table before stand-up Monday. No apology paragraph.

After the call you write the chunking spec as pasteable copy, not as a vibe. Split on H2. Max 800 tokens, 100-token overlap. Keep tables with their heading. Drop nav, cookies, print-this-page. Store path, heading trail, last-updated, permission group. Re-embed on document change. Sample 20 chunks for review before the rest of ingest is funded. You steal today's callout almost word for word because that is what it is for. You attach the four questions you already have and the six you will harvest from Slack on Monday, sitting with two PMs, not with Priya. You note the systematic misses you expect: identifiers without hybrid, tables if anyone splits them again, empty-gold if vector-always-returns-neighbors is left on. Those misses are backlog with an owner, not a reason to buy a larger embedder. You send the note. You do not add a paragraph apologizing for slowing the embed job. Dates slip. Cookie banners cited as policy do not un-cite. That is the argument. You just had it. The practice this afternoon is the ten-question table. Write it as if Priya will try to beat it. She should try.

The interview version is the same argument without the heat. Engineer wanted 500-character tutorial chunks. Sample of twenty had a cookie banner and a split table. Identifier and table questions missed. We froze a mixed eval, chunked by heading, required metadata and ACL fields, and made the twenty-chunk sample a gate. We did not add a reranker, and we did not fix it in the prompt. If you cannot say that, you will say we iterated on chunk size and the interviewer will hear a tutorial. Chunk size is a product knob with a cost and an eval. The argument is the job. Winning it on taste is not the job. Winning it on four misses in the room, a written experiment, and a named knowledge owner is. Steal this Friday. You will have it again on a different corpus with a different Priya. The tutorial will still say 500. Your table will still have POL-1044. Run the table. Do not run the tutorial. That is the whole day, pointed at a real fight instead of at a definition of cosine. Take the fight.

Diagram

Tutorial splitter vs a spec you can staff

A

500-char tutorial

  • Library default, blog-post corpus
  • Splits tables and exception clauses
  • No metadata, no ACL, nav chrome in the index
  • Eval is five PTO paraphrases
  • Reranker-as-rescue, prompt-as-rescue
B

H2 spec (Atlas v1)

  • Split on headings; cap 800 / overlap 100
  • Tables kept whole with the heading
  • Path, date, permission group on every chunk
  • Eval mix: ID, table, stale, empty-gold
  • Twenty-chunk sample is an ingest gate

The left column ships a demo. The right column survives an identifier and a table. Run both on a frozen set before you fund ingest.

Practice

A 10-question retrieval eval (design)

45 minutes

Domain: a project wiki with SOPs, RAID, decision logs, and vendor contracts. You do not need a real index today.

  1. Write 10 questions a new PM would actually ask (not trivia). Mix: exact ID lookup, 'how do we…', 'what did we decide about…', a question whose answer is in a table, a question that should retrieve nothing.
  2. For each, name the source that must come back (invent realistic filenames).
  3. Define success: recall@5. Define a fail: the generator answered without the source.
  4. Write two chunking rules you would demand before anyone embeds a byte.

Done looks like: A table of 10 questions with expected sources, plus two chunking rules. This becomes part of the day 14 artifact.

Check yourself

Attempt in your notes first. Reveal is for after, not during.

  • If the answer is fluent but cites the wrong SOP, where do you look first?

  • Why do exact IDs often fail pure vector search?

  • What metadata belongs on every chunk?

  • What is recall@5 in one sentence?

  • Why is 'should retrieve nothing' a first-class eval row?

  • When do you re-embed the corpus?

Terms from this day

Embedding model
A model that maps text to a vector for similarity, not for chatting.
Vector index
A store that can return nearest neighbors for a query vector (FAISS, pgvector, vendor DBs).
Recall@k
Share of questions for which a needed chunk appears in the top k retrieved results.
Chunking
Splitting documents into retrieval units. A product decision with overlap and structure.
Hybrid search
Combining vector similarity with keyword / filter retrieval.
Reranker
A second-stage model that reorders a shortlist of chunks for a query.

Your notes for day 10

Saved on this device. Use this as the start of the artifact.