<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Lighthouse AI]]></title><description><![CDATA[What I notice while building AI (and with AI).]]></description><link>https://www.lighthousenewsletter.com</link><image><url>https://substackcdn.com/image/fetch/$s_!rokM!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa95a4828-fc18-47b5-bda3-712b30c06098_1024x1024.png</url><title>Lighthouse AI</title><link>https://www.lighthousenewsletter.com</link></image><generator>Substack</generator><lastBuildDate>Mon, 13 Jul 2026 16:35:59 GMT</lastBuildDate><atom:link href="https://www.lighthousenewsletter.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[LLM HOWTO BV]]></copyright><language><![CDATA[en-gb]]></language><webMaster><![CDATA[lighthousenewsletter@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[lighthousenewsletter@substack.com]]></itunes:email><itunes:name><![CDATA[Rafael]]></itunes:name></itunes:owner><itunes:author><![CDATA[Rafael]]></itunes:author><googleplay:owner><![CDATA[lighthousenewsletter@substack.com]]></googleplay:owner><googleplay:email><![CDATA[lighthousenewsletter@substack.com]]></googleplay:email><googleplay:author><![CDATA[Rafael]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Cutting Anthropic Token Costs and Latency with Prompt Caching]]></title><description><![CDATA[Automatic caching vs. explicit breakpoints, the TTL math by access pattern, and the trap where caching costs more than not caching at all.]]></description><link>https://www.lighthousenewsletter.com/p/cutting-anthropic-token-costs-and</link><guid isPermaLink="false">https://www.lighthousenewsletter.com/p/cutting-anthropic-token-costs-and</guid><dc:creator><![CDATA[Rafael]]></dc:creator><pubDate>Sun, 12 Jul 2026 12:45:53 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!9Z7A!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73efd16d-208d-4a2e-bd89-e14cad10cf2b_1920x1280.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9Z7A!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73efd16d-208d-4a2e-bd89-e14cad10cf2b_1920x1280.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9Z7A!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73efd16d-208d-4a2e-bd89-e14cad10cf2b_1920x1280.jpeg 424w, https://substackcdn.com/image/fetch/$s_!9Z7A!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73efd16d-208d-4a2e-bd89-e14cad10cf2b_1920x1280.jpeg 848w, https://substackcdn.com/image/fetch/$s_!9Z7A!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73efd16d-208d-4a2e-bd89-e14cad10cf2b_1920x1280.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!9Z7A!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73efd16d-208d-4a2e-bd89-e14cad10cf2b_1920x1280.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9Z7A!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73efd16d-208d-4a2e-bd89-e14cad10cf2b_1920x1280.jpeg" width="1200" height="800.2747252747253" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/73efd16d-208d-4a2e-bd89-e14cad10cf2b_1920x1280.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:619269,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.lighthousenewsletter.com/i/206688126?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73efd16d-208d-4a2e-bd89-e14cad10cf2b_1920x1280.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9Z7A!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73efd16d-208d-4a2e-bd89-e14cad10cf2b_1920x1280.jpeg 424w, https://substackcdn.com/image/fetch/$s_!9Z7A!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73efd16d-208d-4a2e-bd89-e14cad10cf2b_1920x1280.jpeg 848w, https://substackcdn.com/image/fetch/$s_!9Z7A!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73efd16d-208d-4a2e-bd89-e14cad10cf2b_1920x1280.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!9Z7A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73efd16d-208d-4a2e-bd89-e14cad10cf2b_1920x1280.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p style="text-align: justify;">If you&#8217;re building agents with <strong>Anthropic</strong> models, you&#8217;ve seen the pattern: token prices keep falling, and your invoice keeps climbing. The reason is <strong>structural</strong>. Multi-step, tool-using agents re-send their entire context - system prompt, tool definitions, conversation history - on <em>every</em> turn. An agent that takes 20 turns processes its prefix 20 times. Token volume compounds with every step, and so does <strong>time-to-first-token</strong>, because the model has to re-run its expensive <strong>prefill</strong> pass over all of it before emitting a single output token.</p><h4 style="text-align: justify;">Prefill and KV Cache 101</h4><p style="text-align: justify;">Transformers generate text in two phases. In the <strong>prefill stage</strong>, the model ingests the whole prompt at once: every token computes attention against every token before it, and each layer stores the keys and values it computed for every token. That stored state is the <strong>Key Value (KV) Cache</strong>. Prefill is the expensive part - the attention work grows quadratically with prompt length - and it has to finish before the first output token appears. It&#8217;s where both your input-token cost and your time-to-first-token live.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.lighthousenewsletter.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en-gb&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Field notes for people <em>building</em> AI (and with AI).</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p style="text-align: justify;">In the <strong>decode (or generation) stage</strong>, the model generates one token at a time. Each new token recomputes nothing over the prompt: it reads the KV cache, attends to it, appends its own entry, and repeats. Decode is cheap per step precisely because prefill already did the heavy lifting - which means re-running prefill over a prefix the model has already processed, byte for byte, is pure waste.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tfB-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbd0bc5-691b-4ac7-8c3c-f8c640af7788_2136x1088.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tfB-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbd0bc5-691b-4ac7-8c3c-f8c640af7788_2136x1088.png 424w, https://substackcdn.com/image/fetch/$s_!tfB-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbd0bc5-691b-4ac7-8c3c-f8c640af7788_2136x1088.png 848w, https://substackcdn.com/image/fetch/$s_!tfB-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbd0bc5-691b-4ac7-8c3c-f8c640af7788_2136x1088.png 1272w, https://substackcdn.com/image/fetch/$s_!tfB-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbd0bc5-691b-4ac7-8c3c-f8c640af7788_2136x1088.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tfB-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbd0bc5-691b-4ac7-8c3c-f8c640af7788_2136x1088.png" width="728" height="371" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dcbd0bc5-691b-4ac7-8c3c-f8c640af7788_2136x1088.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:742,&quot;width&quot;:1456,&quot;resizeWidth&quot;:728,&quot;bytes&quot;:2239534,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.lighthousenewsletter.com/i/206688126?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbd0bc5-691b-4ac7-8c3c-f8c640af7788_2136x1088.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!tfB-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbd0bc5-691b-4ac7-8c3c-f8c640af7788_2136x1088.png 424w, https://substackcdn.com/image/fetch/$s_!tfB-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbd0bc5-691b-4ac7-8c3c-f8c640af7788_2136x1088.png 848w, https://substackcdn.com/image/fetch/$s_!tfB-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbd0bc5-691b-4ac7-8c3c-f8c640af7788_2136x1088.png 1272w, https://substackcdn.com/image/fetch/$s_!tfB-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbd0bc5-691b-4ac7-8c3c-f8c640af7788_2136x1088.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Prefill runs once per request and builds the KV cache; decode generates one token at a time against it. Prompt caching re-enters at the cache node, skipping prefill for a byte-identical prefix.</figcaption></figure></div><p style="text-align: justify;"><strong>Prompt caching removes that redundancy</strong>: the provider persists the prefix&#8217;s KV cache and reuses it across requests. It&#8217;s a config change rather than a re-architecture: cached tokens are billed at 10% of the base input price, and the cached portion skips prefill entirely. But it fails silently when misconfigured, and one specific misconfiguration costs <em>more</em> than not caching at all. </p><p style="text-align: justify;">One scoping note up front: everything here uses <strong>Anthropic&#8217;s</strong> pricing model and API, where caching is opt-in and cache <em>writes</em> carry a premium. OpenAI&#8217;s automatic prefix caching has no write surcharge (and therefore can&#8217;t go net-negative), and Gemini&#8217;s context caching sits somewhere in between - the prefix-structure principles transfer, the math doesn&#8217;t.</p><h2>What caching actually does</h2><p>The reuse condition is strict: a cache hit requires the beginning of your prompt to be <strong>byte-identical</strong> to a previous request. On Anthropic&#8217;s API, the multipliers against base input price are:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pWze!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7978d4-3f71-4890-a899-57095c34286e_1282x476.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pWze!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7978d4-3f71-4890-a899-57095c34286e_1282x476.png 424w, https://substackcdn.com/image/fetch/$s_!pWze!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7978d4-3f71-4890-a899-57095c34286e_1282x476.png 848w, https://substackcdn.com/image/fetch/$s_!pWze!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7978d4-3f71-4890-a899-57095c34286e_1282x476.png 1272w, https://substackcdn.com/image/fetch/$s_!pWze!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7978d4-3f71-4890-a899-57095c34286e_1282x476.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pWze!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7978d4-3f71-4890-a899-57095c34286e_1282x476.png" width="1282" height="476" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4b7978d4-3f71-4890-a899-57095c34286e_1282x476.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:476,&quot;width&quot;:1282,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:584495,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.lighthousenewsletter.com/i/206688126?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7978d4-3f71-4890-a899-57095c34286e_1282x476.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!pWze!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7978d4-3f71-4890-a899-57095c34286e_1282x476.png 424w, https://substackcdn.com/image/fetch/$s_!pWze!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7978d4-3f71-4890-a899-57095c34286e_1282x476.png 848w, https://substackcdn.com/image/fetch/$s_!pWze!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7978d4-3f71-4890-a899-57095c34286e_1282x476.png 1272w, https://substackcdn.com/image/fetch/$s_!pWze!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7978d4-3f71-4890-a899-57095c34286e_1282x476.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p style="text-align: justify;">A 5-minute cache write pays for itself on the <em>first</em> reuse - every hit after that is a 90% discount on that portion of the prompt. (The 1-hour write is 2&#215; and needs a couple of reuses to break even; the math is worked out below.) The effect shows up in three places:</p><ol><li><p style="text-align: justify;"><strong>Cost.</strong> In an agent loop, the system prompt, tool definitions, and growing history ride along on <em>every</em> turn. A 20-turn run over a 10K-token prefix pays for ~200K input tokens uncached, the cost-equivalent of ~30K with caching.</p></li><li><p style="text-align: justify;"><strong>Latency.</strong> A cache hit skips prefill over the cached portion. On long contexts this is the difference between a snappy first token and a multi-second stall - same model, same prompt, different shape.</p></li><li><p style="text-align: justify;"><strong>Rate limits.</strong> On the Claude API, cache reads aren&#8217;t deducted against your input-token rate limits. At real traffic volumes this often matters more than the invoice: caching effectively raises your throughput ceiling without a sales conversation.</p></li></ol><h3>Turning it on: two modes, four breakpoints</h3><p style="text-align: justify;">Anthropic gives you two ways in, and they compose.</p><p style="text-align: justify;"><strong>Automatic caching</strong> is one field at the top level of the request. The server places the cache point on the last cacheable block and moves it forward as the conversation grows - each turn reads the previous prefix from cache and writes the new tail:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;c8c2007c-75ff-4239-a2b4-3dde1d1cb7bd&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    cache_control={"type": "ephemeral"},  # that's it
    system=SYSTEM_PROMPT,
    messages=conversation_history,
)</code></pre></div><p style="text-align: justify;">This is the right default for multi-turn conversations: zero bookkeeping, and the breakpoint management (which used to be the fiddly part) is the server&#8217;s problem. One platform caveat: Bedrock doesn&#8217;t support it - there you fall back to explicit breakpoints.</p><p style="text-align: justify;"><strong>Explicit breakpoints</strong> put <span data-color="#37f900" style="color: rgb(55, 249, 0);">cache_control</span> on individual content blocks - up to 4 per request. You want these when different sections of your prompt change at different frequencies: tool definitions change on deploy, a knowledge-base document changes daily, the conversation changes every turn. Each breakpoint marks the end of an independently reusable prefix. Place one on the <em>last</em> block of each section you want cached:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;94b77c04-1286-4cf2-8779-a5d93b946557&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=[
        search_flights_tool,
        book_flight_tool,
        {
            "name": "cancel_flight",
            "description": "Cancel an existing booking.",
            "input_schema": {...},
            # last tool: changes on deploy
            "cache_control": {"type": "ephemeral", "ttl": "1h"},
        },
    ],
    system=[{
        "type": "text",
        "text": LONG_STATIC_INSTRUCTIONS,
        "cache_control": {"type": "ephemeral"},               # changes rarely
    }],
    messages=[{"role": "user", "content": user_message}],     # changes every request
)</code></pre></div><p style="text-align: justify;">Two rules govern the layout. First, the cache hierarchy is fixed: <em>tools, then system, then messages</em>, and a change at any level invalidates that level <strong>and everything after it</strong>. Edit a tool description and your entire cache is cold. Second, breakpoints themselves are free - you pay only for what&#8217;s written and read - so there&#8217;s no cost to using all four.</p><p style="text-align: justify;"><strong>Cache mode composition.</strong> Explicit breakpoints pin the static prefix (tools, system instructions); the top-level automatic flag handles the moving conversation tail. The automatic breakpoint consumes one of the four slots; the rest are yours.</p><p style="text-align: justify;">One mechanic worth knowing before it bites you: cache <em>reads</em> work by looking backward from your breakpoint for a prefix some earlier request already wrote - but only up to <strong>20 blocks</strong> back. In a fast-growing conversation where a single turn adds more than 20 blocks (agents with many parallel tool calls do this), the lookback can walk right past your last write and miss it. The fix is boring: a second breakpoint earlier in the message history, so a write accumulates where the lookback can find it.</p><h2>The TTL math, by access pattern</h2><p style="text-align: justify;">Caching comes in two lifetimes - 5 minutes and 1 hour - and picking wrong is not a rounding error. Here&#8217;s the property that drives everything: <strong>a cache hit refreshes the entry at no additional cost, and the TTL is a minimum lifetime, not a hard expiry.</strong> As long as the gap between consecutive requests stays under the TTL, one write keeps serving hits indefinitely. The moment a gap exceeds the TTL, the entry expires and the next request pays a full write again.</p><p style="text-align: justify;">So the decision variable isn&#8217;t your total traffic - it&#8217;s your <strong>worst-case gap between requests</strong> touching the same prefix. Let&#8217;s make it concrete: a 50K-token stable prefix on a model with $3/MTok base input. Per request, that prefix costs:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ba0w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F185f4d71-a7f4-4374-9881-97950df0bcaa_1274x482.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ba0w!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F185f4d71-a7f4-4374-9881-97950df0bcaa_1274x482.png 424w, https://substackcdn.com/image/fetch/$s_!ba0w!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F185f4d71-a7f4-4374-9881-97950df0bcaa_1274x482.png 848w, https://substackcdn.com/image/fetch/$s_!ba0w!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F185f4d71-a7f4-4374-9881-97950df0bcaa_1274x482.png 1272w, https://substackcdn.com/image/fetch/$s_!ba0w!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F185f4d71-a7f4-4374-9881-97950df0bcaa_1274x482.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ba0w!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F185f4d71-a7f4-4374-9881-97950df0bcaa_1274x482.png" width="1274" height="482" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/185f4d71-a7f4-4374-9881-97950df0bcaa_1274x482.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:482,&quot;width&quot;:1274,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:587134,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.lighthousenewsletter.com/i/206688126?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F185f4d71-a7f4-4374-9881-97950df0bcaa_1274x482.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ba0w!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F185f4d71-a7f4-4374-9881-97950df0bcaa_1274x482.png 424w, https://substackcdn.com/image/fetch/$s_!ba0w!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F185f4d71-a7f4-4374-9881-97950df0bcaa_1274x482.png 848w, https://substackcdn.com/image/fetch/$s_!ba0w!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F185f4d71-a7f4-4374-9881-97950df0bcaa_1274x482.png 1272w, https://substackcdn.com/image/fetch/$s_!ba0w!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F185f4d71-a7f4-4374-9881-97950df0bcaa_1274x482.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p style="text-align: justify;"><strong>Pattern 1 - steady interactive traffic (gaps under 5 minutes).</strong> A live chat session, an agent loop making back-to-back calls. The 5m cache never expires: 1 write + N hits. Twenty requests: $0.1875 + 19 &#215; $0.015 = <strong>$0.47</strong> versus $3.00 uncached - an 84% cut. The 1h TTL buys you nothing here except a more expensive first write. Use 5m.</p><p style="text-align: justify;"><strong>Pattern 2 - sporadic traffic (gaps between 5 minutes and 1 hour).</strong> A scheduled job every 20 minutes, a support conversation where the human replies whenever they reply, a long-running agent whose individual steps take 10 minutes. This is where the 5m cache turns <em>actively harmful</em>: every request arrives to a cold cache, pays the 1.25&#215; write premium, and nothing ever reads it. Seventy-two requests over a day, 20 minutes apart:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IGLo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1316203-4785-4aab-abcc-7bb1c8bff21a_1280x386.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IGLo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1316203-4785-4aab-abcc-7bb1c8bff21a_1280x386.png 424w, https://substackcdn.com/image/fetch/$s_!IGLo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1316203-4785-4aab-abcc-7bb1c8bff21a_1280x386.png 848w, https://substackcdn.com/image/fetch/$s_!IGLo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1316203-4785-4aab-abcc-7bb1c8bff21a_1280x386.png 1272w, https://substackcdn.com/image/fetch/$s_!IGLo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1316203-4785-4aab-abcc-7bb1c8bff21a_1280x386.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IGLo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1316203-4785-4aab-abcc-7bb1c8bff21a_1280x386.png" width="1280" height="386" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b1316203-4785-4aab-abcc-7bb1c8bff21a_1280x386.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:386,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:478150,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.lighthousenewsletter.com/i/206688126?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1316203-4785-4aab-abcc-7bb1c8bff21a_1280x386.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!IGLo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1316203-4785-4aab-abcc-7bb1c8bff21a_1280x386.png 424w, https://substackcdn.com/image/fetch/$s_!IGLo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1316203-4785-4aab-abcc-7bb1c8bff21a_1280x386.png 848w, https://substackcdn.com/image/fetch/$s_!IGLo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1316203-4785-4aab-abcc-7bb1c8bff21a_1280x386.png 1272w, https://substackcdn.com/image/fetch/$s_!IGLo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1316203-4785-4aab-abcc-7bb1c8bff21a_1280x386.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p style="text-align: justify;">The 1h line is 1 write + 71 refreshing hits, because every 20-minute gap is comfortably under the hour. Note the middle row: <strong>caching with the wrong TTL costs more than not caching at all.</strong> (Again, this failure mode is specific to pricing models with write premiums - it can&#8217;t happen on OpenAI&#8217;s automatic caching.) If you see <span data-color="#37f900" style="color: rgb(55, 249, 0);">cache_creation_input_tokens</span> on every request and reads on none, you&#8217;re in that row.</p><p style="text-align: justify;"><strong>Pattern 3 - batch and fan-out workloads.</strong> This is where longer TTLs are the structurally right choice. Batch-shaped work - process 500 documents against the same system prompt, run an evaluation suite, fan an agent out over a work queue - has three properties that all favor 1h:</p><ul><li><p style="text-align: justify;"><em>Gaps are irregular by design.</em> Queue depth varies, some items take 90 seconds and some take 12 minutes, workers pause and resume. A 5m TTL turns every slow item into a cache miss for the next one; 1h absorbs the variance. The write premium is a one-time 2&#215; on the prefix, amortized across the whole batch - on our 500-document run, $0.30 once versus $0.015 &#215; 499 for everything after: <strong>$7.79 total against $75 uncached.</strong></p></li><li><p style="text-align: justify;"><em>Parallelism needs a warm-up step.</em> A cache entry only becomes available once the first response <em>begins</em>. Fire 50 concurrent requests against a cold cache and all 50 miss - and all 50 pay the write premium. The right sequence is: send one request (or a <span data-color="#37f900" style="color: rgb(55, 249, 0);">max_tokens: 0</span> pre-warm, which bills zero output tokens), wait for it, then fan out. With a 1h TTL, one pre-warm covers the whole run even if the fan-out takes 40 minutes.</p></li><li><p style="text-align: justify;"><em>The countercase proves the rule.</em> Anthropic&#8217;s async Message Batches API rejects pre-warming outright, precisely because batch items may execute long after a short-lived cache has expired. When you orchestrate the batch yourself, the TTL is the knob that keeps the cache alive across the run&#8217;s actual duration - so size it to the run, not to the default.</p></li></ul><p style="text-align: justify;">You can also <strong>mix TTLs in one request</strong> - the constraint is that longer TTLs must come first in the prompt. The layout falls out naturally: tools and system prompt on 1h (they&#8217;re shared across every user and every batch item), conversation tail on 5m or automatic (it&#8217;s specific to one fast-moving session).</p><p style="text-align: justify;">Break-even, if you want the general rule instead of worked examples. A 5m write (1.25&#215;) beats uncached from the <strong>first</strong> reuse: 1.35&#215; total against 2&#215; for two uncached requests. A 1h write (2&#215;) is a wash at one reuse (2.1&#215; vs. 2.0&#215;) and wins from the <strong>second</strong> reuse onward - so it needs three total requests within the hour to clearly pay off against no caching at all. Against the <em>5m strategy</em>, 1h wins whenever your typical gap exceeds five minutes, because the comparison is one 2&#215; write against an endless series of 1.25&#215; writes. And if your prefix is reused less than once an hour - don&#8217;t cache it. Not everything should be.</p><h2>Failure modes</h2><p>In production, the failures are rarely in the happy path above. They show up here:</p><p style="text-align: justify;"><strong>Misplacing cache breakpoints</strong>. The most common mistake: putting <span data-color="#37f900" style="color: rgb(55, 249, 0);">cache_control</span> on the <em>final</em> block of the request - the one with the incoming user message. The cache key is a hash of everything up to and including the breakpoint; that block changes every request, so you pay a fresh write every time and never get a read. Automatic caching walks into the same trap if your last block is per-request context. Put the explicit breakpoint on the last block that stays <em>identical</em> across the requests you want sharing a cache.</p><p style="text-align: justify;"><strong>Silent failure below the minimum.</strong> Prompts under the minimum cacheable length (512&#8211;4,096 tokens depending on the model) are processed without caching and <strong>without an error</strong>. The response tells you what actually happened:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;ec8d3895-3f0f-414c-82d7-ac5c0f913e9e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">u = response.usage
# total input = cache reads + cache writes + uncached tokens after the breakpoint
print(u.cache_read_input_tokens, u.cache_creation_input_tokens, u.input_tokens)</code></pre></div><p style="text-align: justify;">If both counters are zero, you&#8217;re not caching. If you see writes on every request and reads on none, your &#8220;stable&#8221; prefix isn&#8217;t stable. If your prefix falls just short of the minimum, padding it up to the threshold is often <em>cheaper</em> than leaving it uncached - reads at 0.1&#215; beat full price quickly.</p><p style="text-align: justify;"><strong>Non-obvious invalidators.</strong> Exact match means exact. Things that break caches without touching a single word of the prompt: toggling a server-side tool like web search (it silently edits the system prompt), changing <span data-color="#37f900" style="color: rgb(55, 249, 0);">tool_choice</span>, adding an image mid-conversation, and - less obviously - a runtime that serializes tool-definition JSON with unstable key ordering, producing a byte-different prefix on every single call. The FAQ at the end has the full invalidation table; when in doubt, diff two consecutive raw requests.</p><h3>Your framework has knobs for this - verify what it sends</h3><p>Most agent frameworks now expose all of the above declaratively. <a href="https://pydantic.dev/docs/ai/models/anthropic/">Pydantic AI</a> is a good example of the shape to look for: independent switches for the conversation, the instructions, and the tool definitions, each with its own TTL:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;081f7035-8ef9-4ef7-a361-a0cdd5533867&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">agent = Agent(
    "anthropic:claude-sonnet-4-6",
    instructions="Detailed static instructions...",
    model_settings=AnthropicModelSettings(
        anthropic_cache=True,                   # auto-cache the growing conversation
        anthropic_cache_instructions=True,      # pin the system prompt (5m)
        anthropic_cache_tool_definitions="1h",  # tools change on deploy: longer TTL
    ),
)</code></pre></div><p style="text-align: justify;">Notice that the TTL-per-section layout maps exactly onto the change-frequency argument from the math section - the framework is just spelling it for you. The good ones also handle details you&#8217;d otherwise get wrong: placing the cache boundary after <em>static</em> instructions but before <em>dynamic</em> ones (the ones interpolating today&#8217;s date or the user&#8217;s name), enforcing the 4-breakpoint budget by dropping the oldest markers first, and falling back gracefully on platforms without automatic caching. LangChain, LiteLLM, and the gateways have equivalents.</p><p style="text-align: justify;">But frameworks add convenience, not correctness. A dynamic instruction in the wrong place breaks the cache no matter who placed the marker, and an abstraction layer is one more place where key ordering or block layout can silently shift. The verification loop is the same regardless of stack: read <span data-color="#37f900" style="color: rgb(55, 249, 0);">cache_write_tokens</span> and <span data-color="#37f900" style="color: rgb(55, 249, 0);">cache_read_tokens</span> off the result, and confirm the ratio matches your mental model of what&#8217;s stable.</p><h3 style="text-align: justify;">What Exactly Invalidates the Cache?</h3><p style="text-align: justify;"><span>The hierarchy is </span><em>tools, then system, then messages</em><span>, and a change at any level invalidates that level plus everything after it:</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KbW5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddad4128-3032-43f3-b9c9-6d0f663c18d3_1284x1570.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KbW5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddad4128-3032-43f3-b9c9-6d0f663c18d3_1284x1570.png 424w, https://substackcdn.com/image/fetch/$s_!KbW5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddad4128-3032-43f3-b9c9-6d0f663c18d3_1284x1570.png 848w, https://substackcdn.com/image/fetch/$s_!KbW5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddad4128-3032-43f3-b9c9-6d0f663c18d3_1284x1570.png 1272w, https://substackcdn.com/image/fetch/$s_!KbW5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddad4128-3032-43f3-b9c9-6d0f663c18d3_1284x1570.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KbW5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddad4128-3032-43f3-b9c9-6d0f663c18d3_1284x1570.png" width="1284" height="1570" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ddad4128-3032-43f3-b9c9-6d0f663c18d3_1284x1570.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1570,&quot;width&quot;:1284,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2035453,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.lighthousenewsletter.com/i/206688126?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddad4128-3032-43f3-b9c9-6d0f663c18d3_1284x1570.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!KbW5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddad4128-3032-43f3-b9c9-6d0f663c18d3_1284x1570.png 424w, https://substackcdn.com/image/fetch/$s_!KbW5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddad4128-3032-43f3-b9c9-6d0f663c18d3_1284x1570.png 848w, https://substackcdn.com/image/fetch/$s_!KbW5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddad4128-3032-43f3-b9c9-6d0f663c18d3_1284x1570.png 1272w, https://substackcdn.com/image/fetch/$s_!KbW5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddad4128-3032-43f3-b9c9-6d0f663c18d3_1284x1570.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Summary</h2><p>Prompt caching reduces to three decisions:</p><ol><li><p style="text-align: justify;"><strong>Where does the stable prefix end?</strong> Put the breakpoint there - on the last block that stays byte-identical across the requests you want sharing a cache.</p></li><li><p style="text-align: justify;"><strong>What is the worst-case gap between requests?</strong> Pick the TTL that exceeds it: 5m for interactive traffic, 1h for sporadic and batch workloads.</p></li><li><p style="text-align: justify;"><strong>Is the prefix reused at least once per TTL window?</strong> If not, don&#8217;t cache it - with a write premium, an unused cache entry costs more than no cache.</p></li></ol><p style="text-align: justify;">Then verify. <span data-color="#37f900" style="color: rgb(55, 249, 0);">cache_read_input_tokens</span> and <span data-color="#37f900" style="color: rgb(55, 249, 0);">cache_creation_input_tokens</span> on every response tell you which of the three decisions is wrong: no reads and no writes means the prefix is below the minimum length; writes on every request means the prefix isn&#8217;t as stable as assumed. The invalidation table above accounts for most of the surprises.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.lighthousenewsletter.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en-gb&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Lighthouse Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[RAG Is Simpler Than You Think]]></title><description><![CDATA[Six approaches to retrieval-based AI, from minimal to elaborate]]></description><link>https://www.lighthousenewsletter.com/p/rag-is-simpler-than-you-think</link><guid isPermaLink="false">https://www.lighthousenewsletter.com/p/rag-is-simpler-than-you-think</guid><dc:creator><![CDATA[Rafael]]></dc:creator><pubDate>Wed, 10 Jun 2026 13:53:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!yF7E!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03ad851c-75b8-4afe-ae16-fd98c8e61836_1920x1280.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yF7E!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03ad851c-75b8-4afe-ae16-fd98c8e61836_1920x1280.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yF7E!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03ad851c-75b8-4afe-ae16-fd98c8e61836_1920x1280.jpeg 424w, https://substackcdn.com/image/fetch/$s_!yF7E!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03ad851c-75b8-4afe-ae16-fd98c8e61836_1920x1280.jpeg 848w, https://substackcdn.com/image/fetch/$s_!yF7E!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03ad851c-75b8-4afe-ae16-fd98c8e61836_1920x1280.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!yF7E!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03ad851c-75b8-4afe-ae16-fd98c8e61836_1920x1280.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yF7E!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03ad851c-75b8-4afe-ae16-fd98c8e61836_1920x1280.jpeg" width="1200" height="800.2747252747253" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/03ad851c-75b8-4afe-ae16-fd98c8e61836_1920x1280.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:169994,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.lighthousenewsletter.com/i/206697618?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03ad851c-75b8-4afe-ae16-fd98c8e61836_1920x1280.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yF7E!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03ad851c-75b8-4afe-ae16-fd98c8e61836_1920x1280.jpeg 424w, https://substackcdn.com/image/fetch/$s_!yF7E!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03ad851c-75b8-4afe-ae16-fd98c8e61836_1920x1280.jpeg 848w, https://substackcdn.com/image/fetch/$s_!yF7E!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03ad851c-75b8-4afe-ae16-fd98c8e61836_1920x1280.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!yF7E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03ad851c-75b8-4afe-ae16-fd98c8e61836_1920x1280.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p style="text-align: justify;">Nowadays, most people seem to over-engineer their RAG stack. They jump straight to embeddings, vector databases, and reranking pipelines. Meanwhile, their users just want to find the doc that says <em>&#8220;How to reset my password.&#8221;</em></p><p style="text-align: justify;">In engineering, there&#8217;s always the right tool for the right problem. In AI Retrieval Systems it&#8217;s not different.</p><h3>The Decision Factors</h3><p style="text-align: justify;">Before we dive into recipes, let&#8217;s establish when you should use each approach. The key factors are:</p><p style="text-align: justify;"><strong>1. Data Freshness Requirements</strong> - Real-time updates (news, social media) favor approaches with easy re-indexing. Daily or weekly updates work well with hybrid approaches. A stable corpus (monthly or quarterly updates) makes pre-embedding sensible.</p><p style="text-align: justify;"><strong>2. Corpus Characteristics</strong> - High churn (more than 10% changes daily) means you should avoid full pre-embedding. Stable documents work fine with pre-embedding. Long-tail distribution (90% never accessed) means on-the-fly wins.</p><p style="text-align: justify;"><strong>3. Query Patterns</strong> - Keyword-heavy queries should start with full-text search. Semantic or conversational queries benefit from embeddings. Mixed patterns need hybrid approaches.</p><p style="text-align: justify;"><strong>4. Scale &amp; Performance</strong> - Less than 1000 queries per day means simple approaches are sufficient. 1K to 10K queries per day requires selective optimization. More than 10K queries per day justifies full optimization.</p><p style="text-align: justify;"><strong>5. Team Capabilities</strong> - No ML expertise means stay with full-text plus query rewriting. Some ML experience makes hybrid search manageable. Having an ML team available makes advanced approaches viable.</p><p style="text-align: justify;">Now, let&#8217;s look at the recipe book. Start at the top. Move down only when you have data proving you need to.</p><h3>Recipe 1: The MVP &#8211; Full-Text Search Only</h3><h4>What it is</h4><p style="text-align: justify;">Good old BM25. Elasticsearch. Postgres full-text search. The stuff that existed before <em>&#8220;embedding&#8221;</em> became a verb.</p><h4>When to use</h4><p style="text-align: justify;">You&#8217;re just starting out. Your users write keyword-style queries <em>(&#8221;pandas merge dataframe&#8221;)</em>. Exact matches matter <em>(&#8221;invoice #12345&#8221;)</em>. You want zero ML complexity. Your corpus has proprietary terminology <em>(more on this later)</em>.</p><h4>Pros</h4><p style="text-align: justify;">Zero API costs. Fast (under 10ms). Easy to debug (you can see exactly why a document matched). Surprisingly effective (handles many use cases). <strong>No chunking strategy needed</strong> &#8211; works with full documents. <strong>No evaluation complexity</strong> &#8211; easy to test and validate. No model deprecation risk (BM25 doesn&#8217;t change).</p><h4>Cons</h4><p style="text-align: justify;">Misses synonyms (&#8221;car&#8221; vs &#8220;automobile&#8221;). Fails on semantic queries (&#8221;How do I...?&#8221;). Can&#8217;t understand intent beyond keywords.</p><h4>Real talk</h4><p style="text-align: justify;">In my experience, this handles a significant portion of use cases. Don&#8217;t skip this step. You might be surprised how far you can get.</p><h4>Why this is underrated</h4><p style="text-align: justify;">When you jump straight to embeddings, you immediately face questions like: What chunk size? (512 tokens? 1024?) What overlap? (50 tokens? 100?) Semantic chunking or fixed-size? How do I evaluate if my chunking is good?</p><p style="text-align: justify;">With full-text search, you skip all of this. Your documents are your documents. Search just works.</p><h3>Recipe 2: Agentic Query Rewriting</h3><h4>What it is</h4><p>Use an LLM to transform messy user queries into clean keyword searches.</p><h4>The insight</h4><p>Most &#8220;semantic search&#8221; problems are actually <em>query formulation</em> problems.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TXCo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F035b8f95-ee55-4356-a00e-d6e5f1e76f4e_1400x400.avif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TXCo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F035b8f95-ee55-4356-a00e-d6e5f1e76f4e_1400x400.avif 424w, https://substackcdn.com/image/fetch/$s_!TXCo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F035b8f95-ee55-4356-a00e-d6e5f1e76f4e_1400x400.avif 848w, https://substackcdn.com/image/fetch/$s_!TXCo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F035b8f95-ee55-4356-a00e-d6e5f1e76f4e_1400x400.avif 1272w, https://substackcdn.com/image/fetch/$s_!TXCo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F035b8f95-ee55-4356-a00e-d6e5f1e76f4e_1400x400.avif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TXCo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F035b8f95-ee55-4356-a00e-d6e5f1e76f4e_1400x400.avif" width="1400" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/035b8f95-ee55-4356-a00e-d6e5f1e76f4e_1400x400.avif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:400,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Flow diagram&quot;,&quot;title&quot;:&quot;Flow diagram&quot;,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Flow diagram" title="Flow diagram" srcset="https://substackcdn.com/image/fetch/$s_!TXCo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F035b8f95-ee55-4356-a00e-d6e5f1e76f4e_1400x400.avif 424w, https://substackcdn.com/image/fetch/$s_!TXCo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F035b8f95-ee55-4356-a00e-d6e5f1e76f4e_1400x400.avif 848w, https://substackcdn.com/image/fetch/$s_!TXCo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F035b8f95-ee55-4356-a00e-d6e5f1e76f4e_1400x400.avif 1272w, https://substackcdn.com/image/fetch/$s_!TXCo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F035b8f95-ee55-4356-a00e-d6e5f1e76f4e_1400x400.avif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4>When to use</h4><p style="text-align: justify;">Users ask questions conversationally. Vocabulary mismatch (users say &#8220;fix bugs&#8221;, docs say &#8220;debugging&#8221;). You have internal jargon (your framework called &#8220;Atlas&#8221;). You want flexibility to iterate quickly on query strategies.</p><h4>Cost</h4><p><em>~$0.001</em> per query (using GPT-4o-mini for query rewriting)</p><h4>The magic</h4><p style="text-align: justify;">An LLM can remove stopwords (&#8221;how do I&#8221; becomes nothing). It can add synonyms (&#8221;car&#8221; becomes &#8220;car automobile vehicle&#8221;). It can translate domain terms (&#8221;speed up code&#8221; becomes &#8220;optimize performance&#8221;). It can decompose complex queries (&#8221;read CSV and plot&#8221; becomes [&#8221;read CSV&#8221;, &#8220;plot data&#8221;]). It can learn from your glossary (via system prompt).</p><h4>Why this is more flexible than embeddings</h4><p>With embeddings, if results aren&#8217;t good, you need to adjust chunking strategy, re-embed entire corpus, run regression tests on your eval set, and hope it improved.</p><p>With query rewriting, if results aren&#8217;t good, you adjust the system prompt. That&#8217;s it. Test immediately.</p><h4>Multi-turn agentic rewriting</h4><p>Even better, you can create a loop:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;cab7064b-a308-4dd1-92e0-a179459cc14b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def agentic_search(query, max_iterations=3):
    for i in range(max_iterations):
        # Rewrite query
        optimized = query_rewriter.rewrite(query, iteration=i)
        
        # Search
        results = bm25_search(optimized)
        
        # Evaluate quality
        quality = evaluate_results(results, query)
        
        if quality &gt; threshold:
            return results
        
        # Agent learns and tries again
        query = refine_based_on_feedback(query, results, quality)
    
    return results</code></pre></div><p>The agent can iterate, learn, and adapt &#8211; all without re-embedding anything.</p><h4>Example: The Proprietary Terminology Problem</h4><p style="text-align: justify;">Say your company has a Python framework called &#8220;Atlas.&#8221; If you use general-purpose embeddings:</p><pre><code><code>General embedding model (trained on internet):
&#8220;Atlas&#8221; = [vectors pointing toward: Greek mythology, maps, geography]
Your actual Atlas docs = [vectors about data processing]
Similarity score: 0.15 (terrible!)</code></code></pre><p style="text-align: justify;">The model has no idea your &#8220;<em>Atlas</em>&#8221; exists. It falls back to what it learned in training. But with query rewriting:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;e647c033-bc6b-4624-994f-e55c0ffcbc0b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">system_prompt = """
  Domain-specific terms (NEVER modify these, use as exact keywords):
    - Atlas: our internal data processing framework
    - Mercury: our messaging system
    - Zeus: our auth service

    Preserve these terms exactly and optimize the rest of the query.
"""

# User: "How do I use Atlas for batch jobs?"
# Agent: "Atlas batch jobs data processing pipeline"
# BM25: Perfect match on "Atlas" &#10003;</code></pre></div><p style="text-align: justify;">For proprietary terms, exact keyword matching beats semantic understanding.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.lighthousenewsletter.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en-gb&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Lighthouse AI! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><h3>Recipe 3: Hybrid Search (Sparse + Dense Reranking)</h3><h4>What it is</h4><p>Use BM25 to get candidates (top 50-100), then rerank with embeddings (top 10).</p><h4>Why this works</h4><p style="text-align: justify;">BM25 is fast and great at keyword matching. Embeddings are good at semantic understanding. Together, they cover each other&#8217;s weaknesses.</p><h4>When to use</h4><p style="text-align: justify;">Users ask semantic questions (&#8221;<em>find alternatives to X&#8221;</em>). BM25 plus query rewriting alone isn&#8217;t cutting it (you have data proving this). You can tolerate 100-500ms latency. Your corpus is relatively stable (not changing every minute).</p><h4>The pipeline</h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DbiV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2237f1d5-80c6-4894-8a29-bac5bb4d2e3d_1400x400.avif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DbiV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2237f1d5-80c6-4894-8a29-bac5bb4d2e3d_1400x400.avif 424w, https://substackcdn.com/image/fetch/$s_!DbiV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2237f1d5-80c6-4894-8a29-bac5bb4d2e3d_1400x400.avif 848w, https://substackcdn.com/image/fetch/$s_!DbiV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2237f1d5-80c6-4894-8a29-bac5bb4d2e3d_1400x400.avif 1272w, https://substackcdn.com/image/fetch/$s_!DbiV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2237f1d5-80c6-4894-8a29-bac5bb4d2e3d_1400x400.avif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DbiV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2237f1d5-80c6-4894-8a29-bac5bb4d2e3d_1400x400.avif" width="1400" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2237f1d5-80c6-4894-8a29-bac5bb4d2e3d_1400x400.avif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:400,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Flow diagram&quot;,&quot;title&quot;:&quot;Flow diagram&quot;,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Flow diagram" title="Flow diagram" srcset="https://substackcdn.com/image/fetch/$s_!DbiV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2237f1d5-80c6-4894-8a29-bac5bb4d2e3d_1400x400.avif 424w, https://substackcdn.com/image/fetch/$s_!DbiV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2237f1d5-80c6-4894-8a29-bac5bb4d2e3d_1400x400.avif 848w, https://substackcdn.com/image/fetch/$s_!DbiV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2237f1d5-80c6-4894-8a29-bac5bb4d2e3d_1400x400.avif 1272w, https://substackcdn.com/image/fetch/$s_!DbiV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2237f1d5-80c6-4894-8a29-bac5bb4d2e3d_1400x400.avif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4>Cost considerations</h4><p style="text-align: justify;">Let&#8217;s do the math with current pricing (OpenAI text-embedding-3-small at $0.02 per 1M tokens):</p><ul><li><p style="text-align: justify;">Embedding 50 docs per query (avg 500 tokens each) means 50 docs &#215; 500 tokens = 25,000 tokens</p></li><li><p style="text-align: justify;">Cost: 25,000 &#215; $0.00002 = ~$0.0005 per query. At 1,000 queries per day &#215; 30 days = ~$15 per month.</p></li></ul><p style="text-align: justify;">Actually pretty reasonable. But there&#8217;s a catch: <strong>latency</strong>.</p><p style="text-align: justify;">Embedding 50 documents on-the-fly adds 200-500ms per query. For user-facing search, that&#8217;s noticeable. This is where the real trade-off lives &#8211; not cost, but speed.</p><h4>Important consideration: The chunking problem returns</h4><p style="text-align: justify;">When you introduce embeddings, you need to decide how to chunk your documents (fixed-size? semantic? by section?). You need to determine what chunk size and overlap to use. You need to handle chunks that span important context.</p><p>This adds complexity that pure full-text search avoids.</p><h3>Recipe 4: On-The-Fly Embedding (The Fresh Data Play)</h3><h4>The insight</h4><p>If your data changes frequently, why pay to re-embed everything?</p><h4>What it is</h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Yl1E!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df52ca-f2e6-46be-b95d-9e3b6aa6621b_1400x400.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Yl1E!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df52ca-f2e6-46be-b95d-9e3b6aa6621b_1400x400.png 424w, https://substackcdn.com/image/fetch/$s_!Yl1E!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df52ca-f2e6-46be-b95d-9e3b6aa6621b_1400x400.png 848w, https://substackcdn.com/image/fetch/$s_!Yl1E!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df52ca-f2e6-46be-b95d-9e3b6aa6621b_1400x400.png 1272w, https://substackcdn.com/image/fetch/$s_!Yl1E!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df52ca-f2e6-46be-b95d-9e3b6aa6621b_1400x400.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Yl1E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df52ca-f2e6-46be-b95d-9e3b6aa6621b_1400x400.png" width="1400" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b8df52ca-f2e6-46be-b95d-9e3b6aa6621b_1400x400.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:400,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Flow diagram&quot;,&quot;title&quot;:&quot;Flow diagram&quot;,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Flow diagram" title="Flow diagram" srcset="https://substackcdn.com/image/fetch/$s_!Yl1E!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df52ca-f2e6-46be-b95d-9e3b6aa6621b_1400x400.png 424w, https://substackcdn.com/image/fetch/$s_!Yl1E!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df52ca-f2e6-46be-b95d-9e3b6aa6621b_1400x400.png 848w, https://substackcdn.com/image/fetch/$s_!Yl1E!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df52ca-f2e6-46be-b95d-9e3b6aa6621b_1400x400.png 1272w, https://substackcdn.com/image/fetch/$s_!Yl1E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df52ca-f2e6-46be-b95d-9e3b6aa6621b_1400x400.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4>When to use</h4><p style="text-align: justify;"><strong>High document churn</strong> (more than 10% of docs updated daily). <strong>Real-time content</strong> (news, social media, live updates). You&#8217;re <strong>experimenting with embedding models</strong> (no re-indexing needed). <strong>Data freshness is critical</strong> (documents must be up-to-date). Small K for reranking (20-50 docs).</p><h4>Math time</h4><pre><code><code>On-the-fly / online (1000 queries/day, 50 docs/query):
- Embedding cost: ~$15/month (ongoing)
- Storage: $0 (just store text)
- Latency: 200-500ms per query
- Freshness: Perfect (always current)
- Model switching: Easy (just change the API call)</code></code></pre><h4>The model deprecation benefit</h4><p>Here&#8217;s something people don&#8217;t talk about enough: <strong>embedding models get deprecated</strong>.</p><p style="text-align: justify;">OpenAI deprecated text-embedding-ada-002 in favor of text-embedding-3. If you pre-embedded 10 million documents with the old model, you now need to re-embed all 10 million documents with the new model, update your vector database, run regression tests on your evaluation set, validate that quality didn&#8217;t degrade, handle the cutover period, and deal with any API changes.</p><h4>With on-the-fly / online embedding</h4><p>You literally just change one line of code. Done.</p><h4>The downside</h4><p style="text-align: justify;">Latency. You&#8217;re embedding documents on every query. This is only viable if you&#8217;re okay with 200-500ms latency, K is small (reranking 20-50 docs, not 500), and your use case favors freshness over speed.</p><h3>Recipe 5: Pre-Embedding with Hot/Cold Tiers (The Pragmatic Play)</h3><h4>What it is</h4><p style="text-align: justify;">Pre-embed frequently accessed documents (&#8221;hot tier&#8221;), embed rarely-accessed documents on-the-fly (&#8221;cold tier&#8221;).</p><h4>The insight</h4><p>Access patterns follow Pareto distribution. 20% of docs get 80% of traffic.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;0791b7d4-b882-4c0d-b593-3a06a96eabb1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Track access patterns
access_counts = Counter()

def adaptive_search(query):
    # BM25 to get candidates
    candidates = bm25_search(query, top_k=100)
    
    # Separate hot and cold
    hot = [d for d in candidates if d.id in hot_tier]
    cold = [d for d in candidates if d.id not in hot_tier]
    
    # Hot docs: use pre-computed embeddings (fast)
    hot_scores = vector_db.similarity_search(query_emb, hot)
    
    # Cold docs: embed on-the-fly (slower, but rare)
    cold_scores = embed_and_score(cold, query_emb)
    
    return merge_and_rank(hot_scores, cold_scores)

## Periodically promote frequently accessed docs to hot tier
def update_tiers_weekly():
    frequently_accessed = [doc_id for doc_id, count 
                          in access_counts.items() 
                          if count &gt; threshold]
    
    # Only re-embed the new hot docs
    newly_hot = set(frequently_accessed) - set(hot_tier)
    embed_and_index(newly_hot)</code></pre></div><h4>When to use</h4><p style="text-align: justify;">Clear access patterns (some docs are accessed way more than others). Medium-to-large corpus (more than 100K documents). Mix of stable and changing content. Need good latency for common queries. Want to minimize re-embedding on model updates.</p><h4>Benefits</h4><p style="text-align: justify;">Fast for 80% of queries (hit pre-embedded cache). Fresh for rarely-accessed docs. Only re-embed hot tier when switching models (20% of corpus). Adapts to changing access patterns. Best latency/cost/flexibility trade-off.</p><h4>The model update story</h4><p>When your embedding model gets deprecated:</p><pre><code><code>Full pre-embedding: Re-embed 1M docs &#215; $0.01 = $10,000 + downtime
Hot/cold tiers: Re-embed 200K docs &#215; $0.01 = $2,000 + minimal downtime
On-the-fly: Change one line of code = $0 + zero downtime</code></code></pre><h3>Recipe 6: Full Pre-Embedding (The Scale Play)</h3><h4>What it is</h4><p style="text-align: justify;">Embed everything upfront. Store in vector database. Search with ANN (approximate nearest neighbors).</p><h4>When to use</h4><p style="text-align: justify;">Very high query volume (more than 10K queries per day). Need under 50ms latency. <strong>Very stable corpus</strong> (under 5% churn per month). Access pattern is broad (no long tail). You have ML team to manage infrastructure.</p><h3>Cost breakdown</h3><pre><code><code>Pre-embedding (1M docs):
- One-time embedding: 1M docs &#215; 500 tokens &#215; $0.00002 = $10
- Storage: 1M &#215; 1536 dims &#215; 4 bytes = 6GB (~$10-30/month)
- Search latency: under 50ms (blazing fast!)
- Freshness: Only as fresh as last re-index</code></code></pre><h4>When NOT to use</h4><p>Documents change frequently (more than 10% per week). You&#8217;re experimenting with embedding models. Low query volume (under 1K queries per day). You haven&#8217;t tried simpler approaches first.</p><h4>The model deprecation nightmare</h4><p style="text-align: justify;">This is where full pre-embedding hurts the most. When you need to switch models, you face <strong>downtime</strong> (your search is degraded while re-embedding), <strong>compute cost</strong> (re-embedding millions of documents), <strong>testing burden</strong> (full regression test suite on new embeddings), <strong>chunking reevaluation</strong> (maybe new model works better with different chunk sizes?), and <strong>risk</strong> (what if the new model is worse for your domain?).</p><p style="text-align: justify;"></p><blockquote><p><em>This is overkill for most systems. I&#8217;ve seen teams spend months optimizing their vector database setup when query rewriting would have solved 90% of their problems.</em></p></blockquote><p></p><p>But if you&#8217;re Pinterest, Shopify, or handling massive scale with a stable corpus, this is where you end up.</p><h2>The Multi-Intent Query Problem</h2><p style="text-align: justify;">Here&#8217;s where things get spicy. We&#8217;ve been discussing single-intent queries: <em>&#8220;How do I merge dataframes?&#8221;</em></p><p style="text-align: justify;">But real users ask stuff like: <strong>&#8220;How do I read a CSV file, clean missing data, and plot the results?&#8221;</strong></p><p style="text-align: justify;">That&#8217;s three separate intents. Searching for this as one query is like trying to find a restaurant that serves pizza, sushi, and tacos. Good luck.</p><h4>The Perplexity Playbook</h4><p>Modern agentic RAG systems (Perplexity, ChatGPT search) handle this elegantly:</p><h4>Query Understanding Agent</h4><p>Break down the query.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;755b8417-5f8d-4e8b-a909-7e48ff54022e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Input: "read CSV, clean data, plot results"

# Agent output:

{
  "query_type": "complex",
  "sub_queries": [
    "pandas read csv file",
    "pandas clean missing data",
    "matplotlib plot dataframe"
  ],
  "dependencies": ["read &gt; clean &gt; plot"]
}</code></pre></div><h4>Parallel Adaptive Processing</h4><p>Route each sub-query optimally</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;d5da3154-f62d-4fc9-9b1b-492b66a9b21b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">Sub-query 1 (simple):
  "pandas read csv"
  Stopwords + lemma, then BM25
  Cost: $0, Latency: 15ms

Sub-query 2 (moderate):
  "pandas clean missing data"
  Synonym expansion, then BM25
  Cost: $0, Latency: 20ms

Sub-query 3 (complex):
  "matplotlib plot dataframe"
  LLM rewrite, then Multi-search
  Cost: $0.001, Latency: 250ms

Total (parallel): $0.002, 250ms (not 285ms!)</code></pre></div><h4>Synthesize</h4><p>Combine results into coherent answer</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;c3b65b5e-d7a4-4f4b-8a68-a421fb91a139&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">Here&#8217;s a complete workflow:

1. Reading CSV Files
   [relevant docs from sub-query 1]
   
2. Cleaning Missing Data
   [relevant docs from sub-query 2]
   
3. Plotting Results
   [relevant docs from sub-query 3]

[Code example combining all three steps]
</code></pre></div><h4>Why this works</h4><p style="text-align: justify;">Each sub-query is focused and precise, leading to better retrieval. Parallel execution means lower latency (max, not sum). Adaptive routing results in lower cost (only complex queries pay for LLM). Structured output provides better UX.</p><h4>Cost comparison</h4><p><strong>Without decomposition</strong></p><ul><li><p>LLM rewriting entire complex query: $0.005</p></li><li><p>Embedding 50 docs: $0.025</p></li><li><p>Total: $0.03</p></li></ul><p><strong>With decomposition</strong></p><ul><li><p>Decompose: $0.001</p></li><li><p>Sub-query 1 (simple): $0</p></li><li><p>Sub-query 2 (simple): $0</p></li><li><p>Sub-query 3 (complex): $0.001</p></li><li><p>Total: $0.002</p></li></ul><p>15x cheaper, better quality.</p><p style="text-align: justify;">This is where agentic retrieval really shines. The agent can intelligently decide which sub-queries need expensive processing (embeddings) and which can be handled with cheap methods (simple preprocessing + BM25).</p><h2>The Decision Tree (Or: When to Use What)</h2><p style="text-align: justify;">Okay, you&#8217;ve read this far. You just want to know: &#8220;What should I build?&#8221;</p><p style="text-align: justify;"><strong>Start here: Do you have search at all?</strong> If not, build BM25 first. Seriously. Stop reading and build it. If you do have search, continue.</p><p style="text-align: justify;"><strong>Measure your baseline.</strong> Run your current search for 2-4 weeks and collect user feedback. Are users happy with the results? If yes, stop. You&#8217;re done. Go ship features. If no, continue.</p><p style="text-align: justify;"><strong>What&#8217;s the main complaint?</strong></p><p style="text-align: justify;">If users say &#8220;Can&#8217;t find docs that clearly exist,&#8221; try query rewriting first. At $0.001 per query with zero re-indexing, it&#8217;s worth testing. Run an A/B test for 2 weeks. If you see good improvement, keep it and you&#8217;re done. If it&#8217;s not enough, continue.</p><p style="text-align: justify;">If users say &#8220;Results are okay but not great,&#8221; A/B test hybrid search (sparse plus embedding rerank). Is the added latency worth it? If yes, decide on implementation. If your data changes frequently, use on-the-fly embedding. If you have clear hot docs, use hot/cold tiers. If you have a stable corpus and high scale, use full pre-embedding. If the latency isn&#8217;t worth it, optimize query rewriting further instead.</p><p style="text-align: justify;">If users say &#8220;Need better semantic understanding,&#8221; use hybrid search and choose your approach based on your situation. High churn (more than 10% per day) means on-the-fly. Medium scale with clear patterns means hot/cold tiers. Massive scale with stable data means full pre-embedding.</p><p><strong>Key decision factors:</strong></p><p style="text-align: justify;">Full-text with query rewriting offers perfect data freshness with low setup complexity and query latency under 50ms. Model switching is trivial, no chunking is needed, and it works for most use cases.</p><p style="text-align: justify;">On-the-fly embedding provides perfect data freshness with low setup complexity but higher query latency of 200-500ms. Model switching is trivial, chunking is needed, and it&#8217;s best for high churn scenarios.</p><p style="text-align: justify;">Hot/cold tiers provide mixed data freshness with medium setup complexity and query latency of 50-100ms. Model switching is easy, chunking is needed, and it offers balanced performance for varied needs.</p><p style="text-align: justify;">Full pre-embedding has stale data until reindex with high setup complexity but query latency under 50ms. Model switching is painful, chunking is needed, and it&#8217;s designed for massive scale operations.</p><p style="text-align: justify;"><strong>The 80/20 rule:</strong> 60% of systems should stop at full-text plus query rewriting. 25% need hybrid with on-the-fly or hot/cold. 10% need full pre-embedding. 5% need custom solutions.</p><p style="text-align: justify;"><strong>Bottomline: Don&#8217;t be the person who builds the 5% solution for a 60% problem.</strong></p><p style="text-align: justify;"></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.lighthousenewsletter.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en-gb&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Lighthouse Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p style="text-align: justify;"></p>]]></content:encoded></item><item><title><![CDATA[Vibe Coders Have a Reading Problem]]></title><description><![CDATA[Writing code has never been cheaper. Reading it has never been more expensive. Most of us haven't repriced.]]></description><link>https://www.lighthousenewsletter.com/p/vibe-coders-have-a-reading-problem</link><guid isPermaLink="false">https://www.lighthousenewsletter.com/p/vibe-coders-have-a-reading-problem</guid><dc:creator><![CDATA[Rafael]]></dc:creator><pubDate>Sat, 09 May 2026 10:55:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!o6UY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f849a87-4a55-45d0-af57-54565287e81d_1508x860.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!o6UY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f849a87-4a55-45d0-af57-54565287e81d_1508x860.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!o6UY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f849a87-4a55-45d0-af57-54565287e81d_1508x860.png 424w, https://substackcdn.com/image/fetch/$s_!o6UY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f849a87-4a55-45d0-af57-54565287e81d_1508x860.png 848w, https://substackcdn.com/image/fetch/$s_!o6UY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f849a87-4a55-45d0-af57-54565287e81d_1508x860.png 1272w, https://substackcdn.com/image/fetch/$s_!o6UY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f849a87-4a55-45d0-af57-54565287e81d_1508x860.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!o6UY!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f849a87-4a55-45d0-af57-54565287e81d_1508x860.png" width="1200" height="684.065934065934" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2f849a87-4a55-45d0-af57-54565287e81d_1508x860.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:830,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:2040373,&quot;alt&quot;:&quot;A developer with their back to the camera faces a wall of eight monitors arranged in a 4&#215;2 grid, each displaying code editors and dashboards, in a dim room lit by dramatic purple and blue ambient light&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://lighthousenewsletter.substack.com/i/206682468?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f849a87-4a55-45d0-af57-54565287e81d_1508x860.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="A developer with their back to the camera faces a wall of eight monitors arranged in a 4&#215;2 grid, each displaying code editors and dashboards, in a dim room lit by dramatic purple and blue ambient light" title="A developer with their back to the camera faces a wall of eight monitors arranged in a 4&#215;2 grid, each displaying code editors and dashboards, in a dim room lit by dramatic purple and blue ambient light" srcset="https://substackcdn.com/image/fetch/$s_!o6UY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f849a87-4a55-45d0-af57-54565287e81d_1508x860.png 424w, https://substackcdn.com/image/fetch/$s_!o6UY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f849a87-4a55-45d0-af57-54565287e81d_1508x860.png 848w, https://substackcdn.com/image/fetch/$s_!o6UY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f849a87-4a55-45d0-af57-54565287e81d_1508x860.png 1272w, https://substackcdn.com/image/fetch/$s_!o6UY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f849a87-4a55-45d0-af57-54565287e81d_1508x860.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Writing code has never been cheaper. Reading it has never been more expensive.</figcaption></figure></div><p></p><p>The page loaded. That should have been the end of it.</p><p style="text-align: justify;">I&#8217;d just shipped a feature in the Next.js agent evaluation suite I&#8217;ve been building. The button did the thing the button was supposed to do. A spinner spun. A row appeared in the database. By every measure I&#8217;d asked for, the work was done.</p><p style="text-align: justify;">I opened DevTools anyway, the way a mechanic listens to an engine after a repair, and clicked the button once.</p><p style="text-align: justify;">The endpoint fired four times.</p><p style="text-align: justify;">I want to describe the feeling, because I think the feeling is the whole point. It wasn&#8217;t alarm. It wasn&#8217;t even surprise. It was the small, almost pleasant click of a suspicion being confirmed before you knew you were suspicious. Four requests, stacked neatly in the panel, identical payloads, identical responses, all triggered by one human gesture.</p><p></p><blockquote><p><em>The feature worked. The feature was also, in some quiet, expensive way, broken.</em></p></blockquote><p></p><p style="text-align: justify;">Nothing in the world had been going to tell me that. Claude Code had done exactly what I asked and nothing more &#8212; it even told me I was <em>absolutely right</em> many times. The page, dutiful and unbothered, had loaded.</p><p style="text-align: justify;">This is what I mean when I say vibe coders have a reading problem. Not literacy in the schoolyard sense. Reading as in: looking at a thing that appears to be working and asking it to prove that it is. Reading as in the network tab, the rendered DOM, the actual query Drizzle produced, the diff you didn&#8217;t ask for.</p><p style="text-align: justify;">Coding assistants optimize for what you ask it to optimize for. They don&#8217;t optimize for what you don&#8217;t think to measure.</p><p style="text-align: justify;">And measuring is reading, and reading is slow, and <em>slowness, right now, feels like the one luxury we&#8217;ve all agreed we no longer have.</em></p><h2>The Network tab</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Srft!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafdd55eb-f0f0-444a-9efb-e86eb6b78425_2400x1600.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Srft!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafdd55eb-f0f0-444a-9efb-e86eb6b78425_2400x1600.webp 424w, https://substackcdn.com/image/fetch/$s_!Srft!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafdd55eb-f0f0-444a-9efb-e86eb6b78425_2400x1600.webp 848w, https://substackcdn.com/image/fetch/$s_!Srft!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafdd55eb-f0f0-444a-9efb-e86eb6b78425_2400x1600.webp 1272w, https://substackcdn.com/image/fetch/$s_!Srft!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafdd55eb-f0f0-444a-9efb-e86eb6b78425_2400x1600.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Srft!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafdd55eb-f0f0-444a-9efb-e86eb6b78425_2400x1600.webp" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/afdd55eb-f0f0-444a-9efb-e86eb6b78425_2400x1600.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Anders Jilden - Unsplash&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Anders Jilden - Unsplash" title="Anders Jilden - Unsplash" srcset="https://substackcdn.com/image/fetch/$s_!Srft!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafdd55eb-f0f0-444a-9efb-e86eb6b78425_2400x1600.webp 424w, https://substackcdn.com/image/fetch/$s_!Srft!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafdd55eb-f0f0-444a-9efb-e86eb6b78425_2400x1600.webp 848w, https://substackcdn.com/image/fetch/$s_!Srft!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafdd55eb-f0f0-444a-9efb-e86eb6b78425_2400x1600.webp 1272w, https://substackcdn.com/image/fetch/$s_!Srft!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafdd55eb-f0f0-444a-9efb-e86eb6b78425_2400x1600.webp 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p style="text-align: justify;">The bug, when I tracked it down, was the kind of thing that gets flagged in a careful code review but that no model I&#8217;ve used has flagged unprompted: a <code>useEffect</code> watching a config object recreated inline on every render. New object identity, new effect run, new fetch, new state, new render, new object. A re-render storm.</p><p style="text-align: justify;">The code looked fine. It was wrong in the specific way that only running code, watched closely, makes obvious &#8212; the Network tab was a waterfall the moment the page loaded.</p><p style="text-align: justify;">I fixed it in two lines &#8212; wrapping the object in a <code>useMemo</code> with an honest dependency array. The fix took eleven minutes. Finding it took instinct I&#8217;m not sure I&#8217;d have if I&#8217;d grown up prompting instead of debugging.</p><p style="text-align: justify;">I&#8217;ve started doing something else, too, and I want to be careful here, because <em>it sounds like a productivity tip and I don&#8217;t mean it as one.</em></p><p style="text-align: justify;"></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.lighthousenewsletter.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en-gb&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Lighthouse AI! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p style="text-align: justify;"></p><h2>Hats</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SX62!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6596ba1-77eb-46dd-9422-773d7c01d9c6_1740x1160.avif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SX62!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6596ba1-77eb-46dd-9422-773d7c01d9c6_1740x1160.avif 424w, https://substackcdn.com/image/fetch/$s_!SX62!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6596ba1-77eb-46dd-9422-773d7c01d9c6_1740x1160.avif 848w, https://substackcdn.com/image/fetch/$s_!SX62!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6596ba1-77eb-46dd-9422-773d7c01d9c6_1740x1160.avif 1272w, https://substackcdn.com/image/fetch/$s_!SX62!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6596ba1-77eb-46dd-9422-773d7c01d9c6_1740x1160.avif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SX62!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6596ba1-77eb-46dd-9422-773d7c01d9c6_1740x1160.avif" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e6596ba1-77eb-46dd-9422-773d7c01d9c6_1740x1160.avif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Long Road - Unsplash&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Long Road - Unsplash" title="Long Road - Unsplash" srcset="https://substackcdn.com/image/fetch/$s_!SX62!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6596ba1-77eb-46dd-9422-773d7c01d9c6_1740x1160.avif 424w, https://substackcdn.com/image/fetch/$s_!SX62!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6596ba1-77eb-46dd-9422-773d7c01d9c6_1740x1160.avif 848w, https://substackcdn.com/image/fetch/$s_!SX62!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6596ba1-77eb-46dd-9422-773d7c01d9c6_1740x1160.avif 1272w, https://substackcdn.com/image/fetch/$s_!SX62!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6596ba1-77eb-46dd-9422-773d7c01d9c6_1740x1160.avif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>&#8220;You&#8217;re totally right!&#8221;, until you&#8217;re not</h3><p style="text-align: justify;">Every couple of features &#8212; every time the codebase crosses some invisible threshold of accumulation &#8212; I stop adding things and ask the agent to take off the implementer hat and put on an adversary&#8217;s. Architect&#8217;s hat. Performance reviewer&#8217;s hat. Security auditor&#8217;s hat. Read this codebase like you&#8217;re being paid to find what&#8217;s wrong with it.</p><p style="text-align: justify;">The default mode of these tools is yes-and &#8212; extend, comply, ship. It&#8217;s a beautiful disposition for getting things done and a slightly dangerous one for noticing that the things you&#8217;re doing have started to rhyme with a mistake.</p><p style="text-align: justify;">The first time I ran one of these passes, the reply came back as a tidy list of things I had been ignoring for a week. Not bugs, exactly. Drifts. An import that had reached across a layer it shouldn&#8217;t. A handler that caught an error and swallowed it. A type that had been quietly load-bearing in a way no one signed off on.</p><p style="text-align: justify;">I read it twice. The feeling wasn&#8217;t quite embarrassment, though there was some of that; it was closer to the warmth of being shown the room you&#8217;ve been living in with the lights finally on. I&#8217;d seen all of it. I had not been looking.</p><h3>The boring refactor that pays you three times</h3><p style="text-align: justify;">What surfaces on these passes is rarely dramatic. A 600-line component that wants to be five smaller ones. A prop drilled through four layers when context would have done. I split things up. I rename. I move a file.</p><p style="text-align: justify;">The codebase gets a little more legible to me, and here is the part I didn&#8217;t expect: it gets a lot more legible to the coding assistant, too. Smaller files mean less unrelated context loaded per task, sharper boundaries, fewer wrong turns on the next request &#8212; Claude Code burns fewer tokens and makes fewer mistakes against a well-modularized repo.</p><blockquote><p><em>A 600-line component is a kind of tax. You pay it every time the agent loads it into context, every time the runtime re-renders it, every time the bundler ships it.</em></p></blockquote><p style="text-align: justify;">But that&#8217;s only half of it, and the other half is the part I keep not seeing written down. A 600-line component is a kind of tax. You pay it every time the agent loads it into context, every time the runtime re-renders it, every time the bundler ships it.</p><p style="text-align: justify;">In Next.js, the same boundaries that shrink your prompt context shrink your client bundles. A 600-line client component drags every one of its imports across the server/client boundary with it; split into focused pieces, most of them turn out not to need to be client components at all. The parts that genuinely need interactivity stay on the client. The rest move to the server, where they cost zero kilobytes to ship. Route-level code splitting suddenly has something to split. The bundle shrinks. The page gets faster.</p><p style="text-align: justify;">None of this required <em>&#8220;doing performance work&#8221;</em> - it required reading the codebase honestly. Architecture, which I used to think of as a gift to my future human self, has quietly become part of the prompt, part of the render, <em>and</em> part of the page weight. Same discipline, three bills it pays.</p><p style="text-align: justify;">The other thing that happens, on these review passes, is that I <em>learn</em>. I learn a TypeScript pattern I&#8217;d been working around for weeks. I learn why my generic was inferring <code>unknown</code> and what to do about it.</p><p style="text-align: justify;">I have, in the last year, picked up more idiomatic TS from these conversations than from any book I&#8217;ve half-finished. Which, given that I&#8217;m coming to TypeScript from a long stretch of Python, is not a low bar.</p><p style="text-align: justify;">The learning happens almost exclusively when I cast the model as a teacher and not a hand. It is the best tutor I&#8217;ve ever had. It is also, in implementer mode, perfectly content to let me stay exactly as ignorant as I was when I started.</p><h2>The question</h2><p style="text-align: justify;">Which brings me to the third thing, and the one I find hardest, because it requires me to be smaller in the conversation than I instinctively want to be.</p><p style="text-align: justify;">I&#8217;ve been trying to stop instructing. I&#8217;ve been trying to ask.</p><p style="text-align: justify;"><em>&#8220;Implement X this way&#8221;</em> is a sentence I used to type a hundred times a day. It feels efficient. It feels like leadership. What it actually does is cap the ceiling of the output at the ceiling of my own knowledge on a Tuesday afternoon.</p><p style="text-align: justify;"><em>&#8220;I&#8217;m thinking of doing X &#8212; what are your thoughts?&#8221;</em> costs the same tokens. Sometimes fewer. The reply is different in a way I find hard to describe except to say there is room in it.</p><p style="text-align: justify;">Room for the model to mention the thing I didn&#8217;t know to ask about. Room for a tradeoff I hadn&#8217;t considered. Room, occasionally, for a flat <em>&#8220;I wouldn&#8217;t do that, and here&#8217;s why,&#8221;</em> which is the sentence I most need and most rarely get when I&#8217;ve already announced my answer in the question.</p><h3>The problem is not you, Claude Code. The problem is me.</h3><p style="text-align: justify;">The conventional worry about AI and engineers is that we&#8217;ll get lazy. I don&#8217;t think that&#8217;s quite right. Lazy people, in my experience, know they&#8217;re being lazy.</p><p style="text-align: justify;">The thing I&#8217;m watching happen, in myself on bad days and in others on most days, is subtler and stranger. It is the feeling of productivity uncoupled from the practice of attention. It is shipping without reading. It is the page loading and the laptop closing and the network tab, unwatched, firing four times into a void no one has agreed to look at.</p><h2>Parting words</h2><p style="text-align: justify;">I should say this plainly. I am writing from <em>inside this</em>, not from <em>some clean perch above it</em>.</p><p style="text-align: justify;">I have shipped code I didn&#8217;t fully read. I have accepted diffs because the tests were green and the hour was late and the difference between green and understood felt, at the time, like a difference I could afford to defer.</p><p style="text-align: justify;">What I&#8217;m trying to hold onto is reading. Not as discipline &#8212; as the kind of curiosity that makes a person open a panel they didn&#8217;t have to open, on a feature that already worked.</p><p style="text-align: justify;">That curiosity is, increasingly, the part of the job no one can do for me. It might be the part that&#8217;s still mine.</p>]]></content:encoded></item><item><title><![CDATA[Cosine Similarity is Dead. Long Live Cosine Similarity.]]></title><description><![CDATA[A practical breakdown of when this metric works for embeddings, when it fails, and what to use instead.]]></description><link>https://www.lighthousenewsletter.com/p/cosine-similarity-is-dead-long-live</link><guid isPermaLink="false">https://www.lighthousenewsletter.com/p/cosine-similarity-is-dead-long-live</guid><dc:creator><![CDATA[Rafael]]></dc:creator><pubDate>Mon, 27 Oct 2025 12:03:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!3hgz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9de11fa-9c61-405c-bbee-fb59a1096545_1200x635.avif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3hgz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9de11fa-9c61-405c-bbee-fb59a1096545_1200x635.avif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3hgz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9de11fa-9c61-405c-bbee-fb59a1096545_1200x635.avif 424w, https://substackcdn.com/image/fetch/$s_!3hgz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9de11fa-9c61-405c-bbee-fb59a1096545_1200x635.avif 848w, https://substackcdn.com/image/fetch/$s_!3hgz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9de11fa-9c61-405c-bbee-fb59a1096545_1200x635.avif 1272w, https://substackcdn.com/image/fetch/$s_!3hgz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9de11fa-9c61-405c-bbee-fb59a1096545_1200x635.avif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3hgz!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9de11fa-9c61-405c-bbee-fb59a1096545_1200x635.avif" width="1200" height="635" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b9de11fa-9c61-405c-bbee-fb59a1096545_1200x635.avif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:635,&quot;width&quot;:1200,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Stylized image of a hand drawing overlapping sine wave curves labeled 'a+b' on a blue and dark background, evoking the math behind similarity functions&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Stylized image of a hand drawing overlapping sine wave curves labeled 'a+b' on a blue and dark background, evoking the math behind similarity functions" title="Stylized image of a hand drawing overlapping sine wave curves labeled 'a+b' on a blue and dark background, evoking the math behind similarity functions" srcset="https://substackcdn.com/image/fetch/$s_!3hgz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9de11fa-9c61-405c-bbee-fb59a1096545_1200x635.avif 424w, https://substackcdn.com/image/fetch/$s_!3hgz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9de11fa-9c61-405c-bbee-fb59a1096545_1200x635.avif 848w, https://substackcdn.com/image/fetch/$s_!3hgz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9de11fa-9c61-405c-bbee-fb59a1096545_1200x635.avif 1272w, https://substackcdn.com/image/fetch/$s_!3hgz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9de11fa-9c61-405c-bbee-fb59a1096545_1200x635.avif 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><em>Cosine similarity ranks embeddings by the angle between them &#8212; but the metric quietly breaks down without proper normalization.</em></figcaption></figure></div><p>Let me tell you about my relationship with linear algebra.</p><p>Back in college, I sat through endless lectures about eigenvectors, matrix decompositions, and vector spaces. I dutifully memorized formulas for exams, promptly forgot them, and thought: <em>&#8220;When will I ever use this?&#8221;</em></p><p><em>Fast forward to 2025</em>. I&#8217;m building a RAG system, knee-deep in semantic search, and suddenly I&#8217;m computing dot products like my career depends on it. Because, well, it kind of does.</p><p>The punchline? That &#8220;useless&#8221; linear algebra is now the foundation of how we search through billions of documents, understand meaning, and make AI systems actually useful.</p><p>So here&#8217;s my redemption arc: Linear algebra isn&#8217;t boring. We just taught it wrong.</p><h3>Why Everyone Suddenly Cares About Vectors</h3><p>If you&#8217;ve built anything with RAG (Retrieval-Augmented Generation) in the last two years, you&#8217;ve heard the pitch: <em>&#8220;Turn your text into embeddings, then search semantically!&#8221;</em></p><p>But what ARE embeddings, really?</p><p>Think of embeddings as coordinates in meaning-space. Every word, sentence, or document gets converted into a list of numbers (a vector) that captures its semantic essence.</p><pre><code>&#8220;cat&#8221; = [0.2, 0.8, 0.1, 0.4, ...]  (hundreds or thousands of numbers)
&#8220;kitten&#8221; = [0.19, 0.79, 0.09, 0.41, ...]  (very close!)
&#8220;car&#8221; = [0.7, 0.1, 0.9, 0.2, ...]  (far away)</code></pre><p>Words with similar meanings get similar coordinates. It&#8217;s like GPS, but for concepts.</p><h2>Why this matters for RAG</h2><p>Traditional keyword search is dumb. If you search for &#8220;automobile&#8221; and the document says &#8220;car,&#8221; you get nothing. Zero. Nada.</p><p>Embeddings are smart. They know &#8220;automobile&#8221; and &#8220;car&#8221; point to nearly the same spot in meaning-space. So when you search, you find what you <em>meant</em>, not just what you <em>said</em>.</p><p>This is why every RAG system worth its salt uses embeddings. They&#8217;re the difference between &#8220;404 Not Found&#8221; and &#8220;Here&#8217;s exactly what you need.&#8221;</p><p>&#183; &#183; &#183;</p><h2>The Tale of Two Similarity Metrics</h2><p style="text-align: justify;">So you&#8217;ve got your vectors. Now you need to answer: <em>&#8220;How similar are these two things?&#8221;</em></p><p style="text-align: justify;">Enter our protagonists: <strong>Cosine Similarity</strong> and <strong>Dot Product</strong>.</p><p style="text-align: justify;">If you remember high school math class (barely), the dot product is that thing where you multiply corresponding elements and add them up. Simple enough.</p><p style="text-align: justify;">But cosine similarity? That&#8217;s where things get interesting.</p><h3>Cosine Similarity</h3><p>Cosine similarity measures the angle between two vectors. If they point in the same direction, similarity = 1. Opposite directions = -1. Perpendicular = 0.</p><pre><code>cosine_similarity(A, B) = (A &#183; B) / (||A|| &#215; ||B||)</code></pre><p>It&#8217;s called <em>&#8220;cosine&#8221;</em> because this formula literally computes the cosine of the angle between vectors. (That&#8217;s right, we&#8217;re doing trigonometry in high-dimensional space. College you would be proud.)</p><p><strong>Why it&#8217;s famous:</strong> It ignores magnitude and focuses purely on direction. A short document and a long document about the same topic get the same similarity score. Fair and intuitive.</p><h3>Dot Product</h3><p>The dot product is simpler: just multiply corresponding elements and sum them up.</p><pre><code>dot_product(A, B) = (a&#8321; &#215; b&#8321;) + (a&#8322; &#215; b&#8322;) + ... + (a&#8345; &#215; b&#8345;)</code></pre><p>No division. No square roots. Just multiply and add.</p><p style="text-align: justify;"><strong>The catch:</strong> It cares about magnitude. Bigger vectors naturally get bigger scores, which can be... problematic.</p><p>&#183; &#183; &#183;</p><h2>The Key Difference (And Why It Matters)</h2><p>Here&#8217;s where it gets interesting.</p><p><strong>Cosine similarity normalizes by magnitude.</strong> Think of it like this:</p><pre><code>A &#183; B = ||A|| &#215; ||B|| &#215; cos(&#952;)</code></pre><p>The dot product equals: <em>(magnitude of A) &#215; (magnitude of B) &#215; (cosine of angle)</em>.</p><p>Cosine similarity divides this by both magnitudes, isolating just the angle:</p><pre><code>cos(&#952;) = (A &#183; B) / (||A|| &#215; ||B||)</code></pre><h3>In plain English</h3><ul><li><p><strong>Dot product</strong> = <em>&#8220;How much do these vectors point in the same direction, weighted by their size?&#8221;</em></p></li><li><p><strong>Cosine similarity</strong> = <em>&#8220;Do these vectors point in the same direction, period?&#8221;</em></p></li></ul><h3>Why This Distinction Matters</h3><p>Imagine two documents:</p><pre><code>Doc 1: &#8220;Cats are cute&#8221;
embedding magnitude = 0.8

Doc 2: &#8220;Cats are cute and fluffy and wonderful and purr and have whiskers...&#8221;
embedding magnitude = 2.3</code></pre><p style="text-align: justify;">Both are about cute cats. They should be considered similar.</p><p style="text-align: justify;"><strong>Using dot product (unnormalized):</strong> Doc 2 gets a way higher score just because it&#8217;s longer. Unfair!</p><p style="text-align: justify;"><strong>Using cosine similarity:</strong> Both get the same score (&#8776; 1.0) because they point in the same direction. Fair!</p><p style="text-align: justify;">This is why cosine similarity became the gold standard for semantic search. It&#8217;s <strong>magnitude-invariant</strong> &#8211; it doesn&#8217;t penalize short documents or favor long ones.</p><h2>Which Approach Should You Use?</h2><p style="text-align: justify;">Before we dive deeper, here&#8217;s the cheat sheet:</p><p style="text-align: justify;">Full-text with rewriting costs about $0.001 per query with latency under 50ms. This works for about 60% of systems and offers zero re-indexing.</p><p style="text-align: justify;">Hybrid on-the-fly approaches cost around $0.015 per query with 200-500ms latency. These work well for high churn data and provide perfect freshness.</p><p style="text-align: justify;">Hot/cold tiers balance cost at $0.005 per query with 50-100ms latency. This offers the best trade-off for balanced needs.</p><p style="text-align: justify;">Full pre-embedding is the cheapest at $0.0005 per query with latency under 50ms. This approach works for massive scale and provides maximum speed.</p><p style="text-align: justify;">Keep this in mind as we explore the theory behind why these trade-offs exist.</p><h2>The Plot Twist: Cosine Similarity Might Be Overkill</h2><p style="text-align: justify;">Here&#8217;s where our story takes a turn.</p><p style="text-align: justify;">Most modern embedding APIs &#8211; <em>OpenAI&#8217;s text-embedding-3, Cohere, Voyage AI</em> &#8211; return <strong>normalized embeddings</strong> by default. (Always check your model&#8217;s documentation, but chances are, they&#8217;re normalized.)</p><p style="text-align: justify;">What does <em>&#8220;normalized&#8221;</em> mean? Every vector has a magnitude of exactly 1.</p><p style="text-align: justify;">And when both vectors have magnitude = 1:</p><pre><code>cosine_similarity(A, B) = (A &#183; B) / (1 &#215; 1) = A &#183; B
</code></pre><p style="text-align: justify;"><strong>The dot product and cosine similarity are IDENTICAL.</strong></p><p style="text-align: justify;">So all that careful normalization we&#8217;ve been doing? That division by magnitudes? <strong>We&#8217;re dividing by 1.</strong> It&#8217;s like wearing a belt with suspenders.</p><h3>Why This Changes Everything</h3><p style="text-align: justify;"><strong>Speed:</strong> Dot product is faster. No square roots, no division. Just multiply and add.</p><p style="text-align: justify;">In high-scale systems (millions of documents, thousands of queries per second), this matters. Vector databases optimize for dot product similarity when working with normalized vectors for exactly this reason.</p><p style="text-align: justify;"><strong>Simplicity:</strong> Less code, fewer bugs. One operation instead of three.</p><p style="text-align: justify;"><strong>Performance:</strong> Every millisecond counts when you&#8217;re paying for compute at scale.</p><h2>So Is Cosine Similarity Dead?</h2><p style="text-align: justify;">Not quite.</p><p style="text-align: justify;"><strong>Use cosine similarity when:</strong> Your embeddings are NOT pre-normalized. You&#8217;re dealing with unnormalized vectors (TF-IDF, custom embeddings). Document length shouldn&#8217;t affect similarity. You need interpretable scores (0-1 range).</p><p style="text-align: justify;"><strong>Use dot product when:</strong> Your embeddings ARE pre-normalized (most modern models). You need maximum speed. You&#8217;re using a vector database with normalized vectors. You&#8217;re processing millions of comparisons.</p><p style="text-align: justify;"><strong>The trick:</strong> Check if your model normalizes embeddings. If yes, use dot product. If no, normalize once at index time, then use dot product forever.</p><p style="text-align: justify;"><strong>Cosine similarity isn&#8217;t dead. It&#8217;s just... optimized away.</strong></p>]]></content:encoded></item></channel></rss>