<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vectorlink Labs</title>
    <description>The latest articles on DEV Community by Vectorlink Labs (@vectorlinklabs).</description>
    <link>https://dev.to/vectorlinklabs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4010795%2Fb8d9db63-2c52-4bc3-855e-c736aa2d40ae.jpg</url>
      <title>DEV Community: Vectorlink Labs</title>
      <link>https://dev.to/vectorlinklabs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vectorlinklabs"/>
    <language>en</language>
    <item>
      <title>We're Probably Caching the Wrong Things in AI</title>
      <dc:creator>Vectorlink Labs</dc:creator>
      <pubDate>Wed, 08 Jul 2026 13:55:06 +0000</pubDate>
      <link>https://dev.to/vectorlinklabs/were-probably-caching-the-wrong-things-in-ai-3c5o</link>
      <guid>https://dev.to/vectorlinklabs/were-probably-caching-the-wrong-things-in-ai-3c5o</guid>
      <description>&lt;p&gt;Most AI caching today focuses on things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Embeddings&lt;/li&gt;
&lt;li&gt;Retrieved documents&lt;/li&gt;
&lt;li&gt;Prompt templates&lt;/li&gt;
&lt;li&gt;Final responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these are valuable.&lt;/p&gt;

&lt;p&gt;But they have one thing in common.&lt;/p&gt;

&lt;p&gt;They optimize &lt;strong&gt;around&lt;/strong&gt; the reasoning process.&lt;/p&gt;

&lt;p&gt;The model still has to read the same context, connect the same ideas, and perform much of the same reasoning on every request.&lt;/p&gt;

&lt;p&gt;It reminds me of the early evolution of web infrastructure.&lt;/p&gt;

&lt;p&gt;At first, servers recomputed everything.&lt;/p&gt;

&lt;p&gt;Eventually, we realized that the computation itself often had value—and caching it became one of the biggest performance improvements.&lt;/p&gt;

&lt;p&gt;I wonder if AI infrastructure is heading in a similar direction.&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What output should we cache?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Maybe the better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What understanding is worth preserving?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Imagine reusing synthesized understanding instead of repeatedly reconstructing it from the same documents.&lt;/p&gt;

&lt;p&gt;That's the direction we've been exploring with &lt;strong&gt;Coalent&lt;/strong&gt; - treating context as something that can be intelligently reused rather than rebuilt from scratch every time.&lt;/p&gt;

&lt;p&gt;If you're curious, &lt;strong&gt;Coalent is live&lt;/strong&gt; : &lt;a href="https://coalent.ai" rel="noopener noreferrer"&gt;https://coalent.ai&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Curious how others are thinking about caching beyond embeddings and response caching. What approaches have you found promising?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>infrastructure</category>
      <category>llm</category>
      <category>performance</category>
    </item>
    <item>
      <title>Token Costs Aren't the Only Cost</title>
      <dc:creator>Vectorlink Labs</dc:creator>
      <pubDate>Tue, 07 Jul 2026 13:10:12 +0000</pubDate>
      <link>https://dev.to/vectorlinklabs/token-costs-arent-the-only-cost-7ec</link>
      <guid>https://dev.to/vectorlinklabs/token-costs-arent-the-only-cost-7ec</guid>
      <description>&lt;p&gt;Whenever people talk about production AI, the conversation usually starts with token costs.&lt;/p&gt;

&lt;p&gt;And that's fair.&lt;/p&gt;

&lt;p&gt;Inference isn't free.&lt;/p&gt;

&lt;p&gt;But I've started wondering if we're optimizing the wrong thing.&lt;/p&gt;

&lt;p&gt;The real cost isn't always the tokens.&lt;/p&gt;

&lt;p&gt;It's the repeated reasoning.&lt;/p&gt;

&lt;p&gt;Think about a typical enterprise AI assistant.&lt;/p&gt;

&lt;p&gt;For every request, it reads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal documentation&lt;/li&gt;
&lt;li&gt;Product manuals&lt;/li&gt;
&lt;li&gt;Company policies&lt;/li&gt;
&lt;li&gt;API documentation&lt;/li&gt;
&lt;li&gt;Knowledge base articles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of that information changes slowly.&lt;/p&gt;

&lt;p&gt;Yet every request asks the model to reconstruct almost the same understanding from scratch.&lt;/p&gt;

&lt;p&gt;It's like hiring a consultant every morning and spending the first hour explaining your business before asking today's question.&lt;/p&gt;

&lt;p&gt;The answer is new.&lt;/p&gt;

&lt;p&gt;The understanding usually isn't.&lt;/p&gt;

&lt;p&gt;That made me think: instead of only optimizing retrieval or reducing token counts, should we also optimize &lt;strong&gt;how understanding itself is reused&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;This question is actually what led us to build &lt;strong&gt;Coalent&lt;/strong&gt; - an open source context engineering library focused on reusing synthesized context rather than repeatedly reconstructing it from the same documents.&lt;/p&gt;

&lt;p&gt;The goal isn't to cache answers.&lt;/p&gt;

&lt;p&gt;It's to preserve the expensive understanding that sits between retrieval and generation, while keeping answers grounded in the latest source data.&lt;/p&gt;

&lt;p&gt;If you're curious, &lt;strong&gt;Coalent is live&lt;/strong&gt; : &lt;a href="https://coalent.ai" rel="noopener noreferrer"&gt;https://coalent.ai&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm curious how others are approaching this.&lt;/p&gt;

&lt;p&gt;Are you optimizing token usage, caching retrieved chunks, reusing synthesized context or taking a completely different approach?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Stop Caching LLM Responses. Cache the Thinking Instead.</title>
      <dc:creator>Vectorlink Labs</dc:creator>
      <pubDate>Mon, 06 Jul 2026 12:53:37 +0000</pubDate>
      <link>https://dev.to/vectorlinklabs/stop-caching-llm-responses-cache-the-thinking-instead-31pg</link>
      <guid>https://dev.to/vectorlinklabs/stop-caching-llm-responses-cache-the-thinking-instead-31pg</guid>
      <description>&lt;p&gt;One of the biggest surprises I had while working on RAG systems wasn't retrieval.&lt;/p&gt;

&lt;p&gt;It was what happened &lt;strong&gt;after&lt;/strong&gt; retrieval.&lt;/p&gt;

&lt;p&gt;Most conversations around RAG optimization focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better embeddings&lt;/li&gt;
&lt;li&gt;Better rerankers&lt;/li&gt;
&lt;li&gt;Better chunking&lt;/li&gt;
&lt;li&gt;Better vector databases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All important.&lt;/p&gt;

&lt;p&gt;But here's something I kept noticing.&lt;/p&gt;

&lt;p&gt;Every query forces the model to reconstruct the same understanding from scratch.&lt;/p&gt;

&lt;p&gt;Imagine your pipeline looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Question
      ↓
Retrieve Documents
      ↓
LLM reads everything
      ↓
Builds understanding
      ↓
Generates answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if another user asks a nearly identical question five minutes later, the model repeats almost all of that expensive reasoning.&lt;/p&gt;

&lt;p&gt;Not because it has to.&lt;/p&gt;

&lt;p&gt;Because we've mostly been caching outputs, not understanding.&lt;/p&gt;

&lt;h2&gt;
  
  
  The expensive part isn't always generation
&lt;/h2&gt;

&lt;p&gt;Generation usually gets the attention because it's visible.&lt;/p&gt;

&lt;p&gt;The hidden cost is rebuilding context.&lt;/p&gt;

&lt;p&gt;The model has to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect documents&lt;/li&gt;
&lt;li&gt;Identify relationships&lt;/li&gt;
&lt;li&gt;Discard irrelevant facts&lt;/li&gt;
&lt;li&gt;Resolve contradictions&lt;/li&gt;
&lt;li&gt;Build a coherent understanding before it can answer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That work gets thrown away after every request.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if we cached the synthesized context?
&lt;/h2&gt;

&lt;p&gt;Instead of caching the final answer, cache the structured understanding that the model already built.&lt;/p&gt;

&lt;p&gt;That allows future requests to reuse reasoning while still generating fresh answers from up-to-date data.&lt;/p&gt;

&lt;p&gt;The benefit is twofold:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fewer tokens&lt;/li&gt;
&lt;li&gt;Lower latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without serving stale responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  This also changes how we think about RAG
&lt;/h2&gt;

&lt;p&gt;Instead of optimizing only retrieval, we start optimizing context construction itself.&lt;/p&gt;

&lt;p&gt;Retrieval finds information.&lt;/p&gt;

&lt;p&gt;Context construction creates understanding.&lt;/p&gt;

&lt;p&gt;Those aren't the same problem.&lt;/p&gt;

&lt;p&gt;This line of thinking is what eventually led us to build &lt;strong&gt;Coalent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Coalent is an open-source context engineering library that focuses on reusing synthesized context instead of repeatedly reconstructing it from the same retrieved documents. &lt;/p&gt;

&lt;p&gt;Rather than caching answers, the goal is to preserve the expensive understanding that happens between retrieval and generation—while still allowing responses to stay grounded in the latest source data.&lt;/p&gt;

&lt;p&gt;As AI applications become more agentic, I think this distinction will matter more and more.&lt;/p&gt;

&lt;p&gt;I'm curious how others are approaching this.&lt;/p&gt;

&lt;p&gt;Are you caching answers, retrieved chunks, synthesized context, or something else entirely?&lt;/p&gt;

&lt;p&gt;If you're interested in exploring this approach,&lt;br&gt;
&lt;strong&gt;Coalent is open source&lt;/strong&gt;: &lt;a href="https://github.com/Vectorlink-Labs/coalent" rel="noopener noreferrer"&gt;https://github.com/Vectorlink-Labs/coalent&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd genuinely love to hear feedback from anyone experimenting with context engineering, RAG optimization or AI agent memory. Different perspectives are always valuable and I'm curious how others are tackling the same challenges.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>rag</category>
      <category>data</category>
    </item>
    <item>
      <title>Stale RAG vs. expensive RAG: how to cache RAG context without serving outdated answers</title>
      <dc:creator>Vectorlink Labs</dc:creator>
      <pubDate>Wed, 01 Jul 2026 12:31:04 +0000</pubDate>
      <link>https://dev.to/vectorlinklabs/stale-rag-vs-expensive-rag-how-to-cache-rag-context-without-serving-outdated-answers-335f</link>
      <guid>https://dev.to/vectorlinklabs/stale-rag-vs-expensive-rag-how-to-cache-rag-context-without-serving-outdated-answers-335f</guid>
      <description>&lt;p&gt;If you run a RAG system in production, you eventually hit a dilemma that has nothing to do with your model and everything to do with your cache.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache the answers&lt;/strong&gt; to save tokens and latency, and one day a source document changes — but your cache keeps cheerfully serving the answer it built from the &lt;em&gt;old&lt;/em&gt; document. Nobody gets an error. The number is just quietly wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache nothing&lt;/strong&gt;, and every single call re-retrieves the same chunks, re-reads them, and re-pays the full context bill to rebuild an understanding you already built five minutes ago for a nearly identical question.&lt;/p&gt;

&lt;p&gt;Stale or expensive. Most teams pick "expensive" because at least it's correct, then bolt on a TTL and hope. This post is about why the TTL doesn't save you, and about two specific, mechanical fixes that let you cache RAG context &lt;em&gt;and&lt;/em&gt; stay fresh. I maintain an open-source library called Coalent that implements both, so I'll use it for the runnable examples — but the two ideas are portable and worth stealing even if you never &lt;code&gt;pip install&lt;/code&gt; anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure mode 1: the stale RAG cache (and why a TTL won't save you)
&lt;/h2&gt;

&lt;p&gt;Here's the standard "answer cache" sitting in front of retrieval:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synthesize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works until &lt;code&gt;billing.md&lt;/code&gt; changes. The refund window goes from 30 days to 14. Your cache has an answer keyed on "what is our refund policy?" that says 30, and it will keep saying 30 for up to an hour — or forever, if the same question keeps refreshing a TTL that never expires under load.&lt;/p&gt;

&lt;p&gt;The reason this is hard is that &lt;strong&gt;the cache key (the query) has no relationship to the thing that changed (the source).&lt;/strong&gt; You cached an answer; you threw away the fact that this particular answer was &lt;em&gt;derived from&lt;/em&gt; &lt;code&gt;billing.md&lt;/code&gt;. So when &lt;code&gt;billing.md&lt;/code&gt; changes, you have no way to find the answers that depended on it.&lt;/p&gt;

&lt;p&gt;The TTL is a confession that you can't answer the question "which cached units are now wrong?", so you guess a time and blow everything away on a timer. Too short and you've rebuilt a perfectly good cache for nothing. Too long and you serve stale data. There is no good number, because freshness is an &lt;em&gt;event&lt;/em&gt;, not a duration.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix: invalidate by provenance, not by clock
&lt;/h3&gt;

&lt;p&gt;The fix is to record, for every cached unit, &lt;strong&gt;which sources it cited&lt;/strong&gt; — its provenance — and to invalidate by set membership when a source changes. No source change, no invalidation. A source changes, and &lt;em&gt;only&lt;/em&gt; the units that actually cited it get marked dirty.&lt;/p&gt;

&lt;p&gt;That requires a cache that keeps the derivation link instead of discarding it. In Coalent, the cached unit retains the source spans it was built from, so a change event is a reverse-index lookup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;coalent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SemanticCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LLMSynthesizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OpenAIProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbedder&lt;/span&gt;

&lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SemanticCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                               &lt;span class="c1"&gt;# bring your own: vector DB, GraphRAG, tools
&lt;/span&gt;    &lt;span class="nc"&gt;LLMSynthesizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OpenAIProvider&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;embedder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;OpenAIEmbedder&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;what is our refund policy?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# r.context["understanding"]  -&amp;gt; summary + claims + facts, just the decision-relevant slice
# r.cache_hit                 -&amp;gt; served from cache or freshly built
&lt;/span&gt;
&lt;span class="c1"&gt;# A source changed. This is surgical, not a flush:
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source_changed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;billing.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...refunds within 14 days...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# result.dirtied            -&amp;gt; units that actually cited billing.md AND whose content changed
# result.skipped_unchanged  -&amp;gt; matched the source but the bytes were identical -&amp;gt; left fresh
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details that matter in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Skip the no-ops.&lt;/strong&gt; &lt;code&gt;source_changed&lt;/code&gt; hashes the new text and compares it to the hash stored on each unit's provenance. If a CI job rewrites &lt;code&gt;billing.md&lt;/code&gt; byte-for-byte, nothing is invalidated. You don't pay to rebuild understanding that didn't change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lazy rebuild.&lt;/strong&gt; A dirtied unit isn't rebuilt immediately. It's marked stale and re-materializes on the &lt;em&gt;next read that asks for it&lt;/em&gt;. Sources that change but are never queried again cost you nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest caveat: invalidation today is &lt;strong&gt;per-artifact&lt;/strong&gt;. If you chunk one giant doc into 200 pieces that all share a single &lt;code&gt;artifact_id&lt;/code&gt;, a change anywhere in that doc dirties everything derived from it — more rebuilding than strictly necessary. Span-level granularity (invalidate only the units whose specific cited span changed) is the planned improvement. If your sources map roughly one-document-to-one-id, you're already in the good case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure mode 2: the lossy-summary gap (a cache hit worse than plain RAG)
&lt;/h2&gt;

&lt;p&gt;Now the &lt;em&gt;other&lt;/em&gt; direction. To cut tokens, you cache a &lt;strong&gt;summary&lt;/strong&gt; of the retrieved context instead of the raw chunks. Great — until a later query needs a detail the summary dropped.&lt;/p&gt;

&lt;p&gt;You retrieved a dense billing doc, summarized it to "Refunds are available within the policy window, with some exceptions," and cached that. Then someone asks: &lt;em&gt;"Can I get a refund on a gift-card purchase?"&lt;/em&gt; The summary is on-topic, so it "hits" — and answers from a blob that silently dropped the gift-card exception. &lt;strong&gt;Your cache just returned an answer worse than plain RAG would have&lt;/strong&gt;, because plain RAG still had the raw chunk with the exception in it. The cache hit, and the hit was a downgrade.&lt;/p&gt;

&lt;p&gt;This is the subtle one. Staleness is at least &lt;em&gt;detectable&lt;/em&gt; in principle. The lossy-summary gap looks like a perfectly healthy cache hit. Your hit rate goes up. Your accuracy goes down. Nobody notices until a customer does.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix: keep the raw, and add a semantic coverage floor
&lt;/h3&gt;

&lt;p&gt;Two parts.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;never throw away the raw evidence.&lt;/strong&gt; Cache the cheap summarized understanding for the common case, but keep the chunks attached to the unit so the detail is always one hop away.&lt;/p&gt;

&lt;p&gt;Second — and this is the part most "semantic cache" implementations skip — &lt;strong&gt;measure whether the hit actually covers the query before you trust it.&lt;/strong&gt; A cache hit means "a unit on this topic exists," not "this unit answers &lt;em&gt;this&lt;/em&gt; question." Those are different claims, and conflating them is exactly the gift-card bug.&lt;/p&gt;

&lt;p&gt;Coalent scores coverage per claim. It embeds each claim/fact in a unit separately and takes the max cosine similarity against the query. If the query's best match against any stored claim is below a floor, the hit &lt;strong&gt;escalates&lt;/strong&gt;: it pulls fresh raw evidence for that specific query and answers from it — still counted as a hit, but no longer answering from a summary that's missing the point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;can I get a refund on a gift-card purchase?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coverage&lt;/span&gt;    &lt;span class="c1"&gt;# 0..1: how well the unit's best claim addresses THIS query
&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;escalated&lt;/span&gt;   &lt;span class="c1"&gt;# True -&amp;gt; coverage was under the floor, so it pulled fresh raw
&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;raw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# the raw evidence, present once escalated
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing to be precise about: escalation only kicks in on a &lt;strong&gt;warm hit&lt;/strong&gt; — a unit on this topic already exists but doesn't cover this &lt;em&gt;specific&lt;/em&gt; query. A cold, first-ever query is just a normal miss that retrieves and builds fresh, so it gets the full evidence anyway. Escalation is for the dangerous in-between: when the cache &lt;em&gt;has&lt;/em&gt; something on-topic but incomplete, and would otherwise answer from it.&lt;/p&gt;

&lt;p&gt;Why per-claim and not one similarity score over the whole summary? Because a single embedding of the whole unit is a centroid. An on-topic-but-uncovered query ("gift-card refund") sits close to the centroid of a refund unit and looks "covered" even though the &lt;em&gt;specific&lt;/em&gt; fact is missing. Scoring each claim separately is what catches the gap. And because it's semantic, "nicked card" still matches a claim about a "stolen card" — a keyword gate would miss that. (That semantic behaviour assumes a real embedder — &lt;code&gt;OpenAIEmbedder&lt;/code&gt; or a local model; on the zero-dependency &lt;code&gt;HashingEmbedder&lt;/code&gt; default, matching degrades to keyword overlap, so use a real embedder in production.)&lt;/p&gt;

&lt;p&gt;The cheap cosine check decides the clear cases for free. For the ambiguous middle band, you can opt into a stricter &lt;code&gt;coverage_scorer&lt;/code&gt; (a cross-encoder or LLM-entailment check) that only fires on the genuinely uncertain hits — containment-grade accuracy without paying for it on every query.&lt;/p&gt;

&lt;p&gt;The metric that ties it together is the &lt;strong&gt;escalation rate&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;escalation_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# fraction of hits that had to fall back to raw
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that climbs, your cached understanding is systematically under-covering real queries — a signal to deepen what you cache (or lower the floor). It's also your honest readout of when the cache has quietly degraded into doing plain RAG. You want to &lt;em&gt;see&lt;/em&gt; that, not discover it from a support ticket.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this actually buys you (with the caveats)
&lt;/h2&gt;

&lt;p&gt;I benchmarked this against feeding full retrieved context to the model on every call, with an independent gpt-4o judge, on number-dense documents, with a source change midway through the run to test invalidation. Results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;~66% fewer context tokens per read&lt;/strong&gt; typically, up to &lt;strong&gt;~75%&lt;/strong&gt; on large/dense docs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accuracy matched&lt;/strong&gt; full-context RAG: 100% on gpt-4o-mini. On a stricter gpt-4o grader, ~93% vs full-context's 96% in that run — Coalent occasionally drops a &lt;em&gt;secondary&lt;/em&gt; qualifier from an answer. That's a real gap, not a rounding error, and worth knowing before you ship.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So: roughly two-thirds the context tokens per read at near-parity accuracy, and — the part the token number doesn't show — it stays correct when a source changes, because invalidation is provenance-scoped instead of a TTL guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway you can use anywhere
&lt;/h2&gt;

&lt;p&gt;You don't need my library to apply the two ideas. The structural lessons are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Don't key a cache on the query alone — record what each cached unit was derived from.&lt;/strong&gt; Provenance is what lets you invalidate by event (a real source change) instead of by clock (a hopeful guess). It also turns invalidation into set membership, which has zero false negatives over the provenance you recorded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A cache hit is a hypothesis, not a guarantee.&lt;/strong&gt; Keep the raw evidence, and put a coverage check under every hit so an on-topic-but-incomplete unit escalates instead of confidently answering from less than RAG had.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Get those two right and the stale-vs-expensive dilemma mostly dissolves: you cache aggressively for reuse, invalidate surgically for freshness, and fall back to raw exactly when — and only when — the cache can't actually answer the question.&lt;/p&gt;

&lt;p&gt;Coalent is Apache-2.0, pure Python, zero required dependencies, if you want a reference implementation to read or build on: &lt;a href="https://github.com/Vectorlink-Labs/coalent" rel="noopener noreferrer"&gt;github.com/Vectorlink-Labs/coalent&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'm Nisarg Pujara, and I maintain Coalent — so take the benchmark numbers as an invitation to run your own on your own corpus, which is the only benchmark that should ever convince you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;Interested in trying Coalent or learning more?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://coalent.ai" rel="noopener noreferrer"&gt;https://coalent.ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Vectorlink-Labs/coalent" rel="noopener noreferrer"&gt;https://github.com/Vectorlink-Labs/coalent&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;PyPI:&lt;/strong&gt; &lt;a href="https://pypi.org/project/coalent/" rel="noopener noreferrer"&gt;https://pypi.org/project/coalent/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;Documentation:&lt;/strong&gt; &lt;a href="https://coalent.ai/docs" rel="noopener noreferrer"&gt;https://coalent.ai/docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>llm</category>
      <category>context</category>
    </item>
  </channel>
</rss>
