<?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: Ishwar</title>
    <description>The latest articles on DEV Community by Ishwar (@ishwar170695).</description>
    <link>https://dev.to/ishwar170695</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%2F3960939%2F21cc9745-e3d2-4cc1-82ac-11d456cd074a.jpg</url>
      <title>DEV Community: Ishwar</title>
      <link>https://dev.to/ishwar170695</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ishwar170695"/>
    <language>en</language>
    <item>
      <title>I Rebuilt My AI Legal Assistant After Learning Why Vector-Only RAG Wasn't Enough</title>
      <dc:creator>Ishwar</dc:creator>
      <pubDate>Sat, 11 Jul 2026 11:18:00 +0000</pubDate>
      <link>https://dev.to/ishwar170695/i-rebuilt-my-ai-legal-assistant-after-learning-why-vector-only-rag-wasnt-enough-52p6</link>
      <guid>https://dev.to/ishwar170695/i-rebuilt-my-ai-legal-assistant-after-learning-why-vector-only-rag-wasnt-enough-52p6</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-07-09"&gt;Weekend Challenge: Passion Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;LawDecoder&lt;/strong&gt; is an AI-powered legal assistant that explains Indian laws in plain language while showing the exact legal provisions used to generate each answer.&lt;/p&gt;

&lt;p&gt;Unlike many dense-only retrieval prototypes, LawDecoder focuses on retrieval quality. It was born out of a real-world failure. One day I asked my original prototype:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Someone forged my signature."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first law it retrieved wasn't about forgery. It was about counterfeit coins.&lt;/p&gt;

&lt;p&gt;That was the moment I realized the problem wasn't the LLM—it was my retrieval pipeline. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The surprising part:&lt;/strong&gt; I didn't change the LLM. Nearly all of the massive retrieval accuracy gains came from redesigning the search architecture to combine semantic search, keyword search, Reciprocal Rank Fusion (RRF), and deterministic domain reranking.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Here is a visual walkthrough of the production UI, showing how the hybrid search handles citations and developer metrics:&lt;/p&gt;

&lt;h3&gt;
  
  
  1️⃣ User Chat Interface
&lt;/h3&gt;

&lt;p&gt;Clean, legal explanation interface for end users:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9galq6jybdykddw7o6rh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9galq6jybdykddw7o6rh.png" alt="LawDecoder Streamlit user chat landing page showing response layout" width="800" height="582"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2️⃣ Structured Offence &amp;amp; Citation Details
&lt;/h3&gt;

&lt;p&gt;Deduplicated citations with developer metrics visible in Developer Mode:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fomxclvmr5iw3dsnkgwn1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fomxclvmr5iw3dsnkgwn1.png" alt="LawDecoder citation view in developer mode displaying RRF ranks and selection reasons" width="800" height="582"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  3️⃣ System Evaluation Dashboard
&lt;/h3&gt;

&lt;p&gt;Performance comparisons and technical architecture story:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmuyusdsz4zxf0fq1vd4c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmuyusdsz4zxf0fq1vd4c.png" alt="LawDecoder developer dashboard showing performance latency and accuracy benchmarks comparison table" width="800" height="582"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The complete implementation—including SQLite ingestion, FTS5 indexing, Reciprocal Rank Fusion, evaluation queries, and benchmark samples—is available on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ishwar170695" rel="noopener noreferrer"&gt;
        ishwar170695
      &lt;/a&gt; / &lt;a href="https://github.com/ishwar170695/LawDecoder" rel="noopener noreferrer"&gt;
        LawDecoder
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;⚖️ LawDecoder: A Case Study in Hybrid Retrieval for Legal AI&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;LawDecoder is an AI-powered legal assistant that explains Indian laws in plain language while showing the exact legal provisions used to generate each answer.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Unlike many dense-only retrieval demos, LawDecoder focuses on retrieval quality. It combines semantic search, keyword search, Reciprocal Rank Fusion (RRF), and deterministic reranking to improve legal citation accuracy.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;🔍 &lt;strong&gt;Hybrid Retrieval:&lt;/strong&gt; Fuses SQLite FTS5 (sparse BM25 keyword matching) and local dense vector embeddings.&lt;/li&gt;
&lt;li&gt;🔀 &lt;strong&gt;Reciprocal Rank Fusion (RRF):&lt;/strong&gt; Fuses sparse and dense search rankings to prioritize matches returned by both.&lt;/li&gt;
&lt;li&gt;🎯 &lt;strong&gt;Domain Reranker:&lt;/strong&gt; Deterministically demotes irrelevant matches (like counterfeit stamp/coin sections) and boosts direct offences (like document forgery acts) for signature queries.&lt;/li&gt;
&lt;li&gt;🧾 &lt;strong&gt;Citation Transparency:&lt;/strong&gt; Shows the exact acts, sections, and selection details for every explanation.&lt;/li&gt;
&lt;li&gt;🔧 &lt;strong&gt;Developer Mode:&lt;/strong&gt; Toggle view to inspect RRF ranks and retrieval selection reasons.&lt;/li&gt;
&lt;li&gt;🤖 &lt;strong&gt;Empathetic AI:&lt;/strong&gt; Structured…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ishwar170695/LawDecoder" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Failure: Tracing the Cause
&lt;/h3&gt;

&lt;p&gt;The original vector-only (v1) search failed on a simple forgery query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query:
"Someone forged my signature"

Top result (v1):
❌ BNS Section 180 — Possession of counterfeit coin

Expected:
✅ BNS Section 336 — Forgery
✅ BNS Section 340 — Using forged document
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The embedding model wasn't "wrong"—it placed semantically close concepts close together in vector space. But in legal search, &lt;em&gt;semantically similar&lt;/em&gt; isn't the same as &lt;em&gt;legally relevant&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Because "forgery" and "counterfeit" ended up close in embedding space, the retriever ranked counterfeit coin and government stamp provisions above the actual definition of document forgery. The retriever generalized too aggressively, missing the direct definition of forgery (&lt;code&gt;BNS Section 336&lt;/code&gt;) because the word "signature" was semantically distant from generic statutory descriptions of the offence.&lt;/p&gt;

&lt;p&gt;Additionally, caching large raw text strings (text, titles, act names) in a JavaScript array caused the Node.js process to consume over &lt;strong&gt;320 MB of RAM&lt;/strong&gt; at startup.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Redesigned Retrieval Pipeline
&lt;/h3&gt;

&lt;p&gt;I redesigned the retrieval pipeline to combine sparse and dense search methods.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3apjvn319446pjfabujx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3apjvn319446pjfabujx.png" alt="LawDecoder hybrid retrieval architecture diagram: SQLite FTS5 sparse keyword index and dense vector ONNX pipeline merged via Reciprocal Rank Fusion (RRF) and Domain Reranker" width="580" height="823"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The LLM remained almost unchanged. Nearly all improvements came from redesigning retrieval.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  1. SQLite + FTS5 Sparse Indexing
&lt;/h4&gt;

&lt;p&gt;I moved all 4,892 legal sections out of JSON files and persisted them in a local &lt;strong&gt;SQLite&lt;/strong&gt; database. A virtual &lt;strong&gt;FTS5 index&lt;/strong&gt; handles exact-match keyword indexing (BM25 ranking), ensuring precise matches for terms like &lt;em&gt;"Section 65"&lt;/em&gt;, &lt;em&gt;"forgery"&lt;/em&gt;, or &lt;em&gt;"signature"&lt;/em&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Lightweight Vector Memory Cache
&lt;/h4&gt;

&lt;p&gt;I stripped all text metadata from Node.js memory. The startup script now loads only the &lt;code&gt;id&lt;/code&gt; (string) and the coordinate list—pre-processed into a compact &lt;code&gt;Float32Array&lt;/code&gt; object—into RAM. The actual text content remains on disk in SQLite and is hydrated for the top 5 matched sections on-demand, reducing memory usage by &lt;strong&gt;85%&lt;/strong&gt; (from 320 MB to &lt;strong&gt;48 MB&lt;/strong&gt;).&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Reciprocal Rank Fusion (RRF)
&lt;/h4&gt;

&lt;p&gt;Fuses the top 50 semantic matches (dense) and top 50 keyword matches (sparse) into a single unified list using the reciprocal ranks of both retrievers. Here is the core JS implementation of the RRF merge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rrfScores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Standard constant for RRF&lt;/span&gt;

&lt;span class="c1"&gt;// Process vector ranking positions (dense)&lt;/span&gt;
&lt;span class="nx"&gt;vectorRankings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rrfScores&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="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Process FTS5 keyword ranking positions (sparse) and add to scores&lt;/span&gt;
&lt;span class="nx"&gt;ftsRankings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rrfScores&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="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;rrfScores&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="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;existingScore&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Sort matched IDs based on fused RRF score&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mergedRanking&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rrfScores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Top 20 candidates for reranking&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Domain Reranker (Deterministic Guardrail)
&lt;/h4&gt;

&lt;p&gt;It evaluates the top 20 candidates returned by the RRF step. This is a deterministic rule-based reranker tailored to the legal domain—not a learned neural cross-encoder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  If the query is document/signature forgery-related, it checks if a retrieved document is a coin or banknote counterfeit section. If yes, it &lt;strong&gt;penalizes the score by 99%&lt;/strong&gt; (&lt;code&gt;* 0.01&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  It &lt;strong&gt;boosts direct document forgery offences&lt;/strong&gt; (containing &lt;code&gt;"forgery"&lt;/code&gt; or &lt;code&gt;"forged"&lt;/code&gt;) by &lt;strong&gt;300%&lt;/strong&gt; (&lt;code&gt;* 3.0&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  It filters out duplicate sections using content snippet prefixes.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Heuristic Domain Reranking&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isDocumentForgeryRelated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isCoinOrStampOrCurrency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
    &lt;span class="nx"&gt;titleLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;coin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;titleLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stamp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; 
    &lt;span class="nx"&gt;titleLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;currency&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;titleLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bank-note&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;contentLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;coin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;contentLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stamp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; 
    &lt;span class="nx"&gt;contentLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;currency-note&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isCoinOrStampOrCurrency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;adjustedScore&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// heavily penalize counterfeit coin/stamps (reduce by 99%)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;titleLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;forgery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;titleLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;forged&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;adjustedScore&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// strong boost for direct forgery definitions/offences&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Performance &amp;amp; Evaluation Benchmarks
&lt;/h3&gt;

&lt;p&gt;To evaluate the redesign, I assembled a benchmark of 100 manually verified legal queries spanning criminal law, cybercrime, family law, consumer protection, and procedural law.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;v1 (Naive Vector RAG)&lt;/th&gt;
&lt;th&gt;v2.1 (Hybrid Search - Current)&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Search Engine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dense Vector (Linear JSON scan)&lt;/td&gt;
&lt;td&gt;Hybrid (SQLite FTS5 + Dense Vector + RRF + Reranker)&lt;/td&gt;
&lt;td&gt;Major retrieval precision upgrade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Avg. Query Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;466 ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12 ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;97.4% speedup&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory Cache Footprint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~320 MB&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~48 MB&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85.0% RAM savings&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Duplicate Citations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Present (up to 40% overlaps)&lt;/td&gt;
&lt;td&gt;Deduplicated (0% overlaps)&lt;/td&gt;
&lt;td&gt;Verified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Top-5 Relevant Retrieval Rate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~68%&lt;/td&gt;
&lt;td&gt;~91%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+23% accuracy gain&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Latency is based on 100 benchmark queries. Memory is process-level heap size at startup. Accuracy is evaluated on top-5 target matches using a manually verified benchmark dataset of 100 queries.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;None of these improvements required changing the language model. The gains came almost entirely from retrieval engineering. For this benchmark query, the top retrieved references aligned with the expected legal provisions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;BNS 340:&lt;/strong&gt; Forged document and using it as genuine&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;BNS 336:&lt;/strong&gt; Forgery definition and penalty&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;BNS 339:&lt;/strong&gt; Possession of forged document&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;BNS 335:&lt;/strong&gt; Making a false document&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Evidence Act Section 65:&lt;/strong&gt; Proof of signature and handwriting&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Lessons Learned
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Retrieval quality sets the upper bound for RAG quality:&lt;/strong&gt; The LLM can only reason over what you retrieve. Improving retrieval turned out to be far more impactful than switching models.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Dense embeddings alone are rarely enough for domain-specific search:&lt;/strong&gt; Hybrid retrieval is often a better default because it combines exact keyword matching with semantic understanding.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;SQLite + FTS5 is a powerhouse:&lt;/strong&gt; For corpora under 100,000 documents, SQLite FTS5 and typed arrays in Node.js deliver sub-15ms latency on CPU with zero operational complexity.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Simple deterministic rerankers work:&lt;/strong&gt; They can eliminate domain-specific retrieval errors without requiring another neural model.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  What's Next
&lt;/h3&gt;

&lt;p&gt;If I continue evolving this project, the next improvements I'd explore are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Cross-encoder reranking:&lt;/strong&gt; Integrate lightweight cross-encoders (e.g. BGE reranker) for advanced ranking.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Metadata-aware retrieval:&lt;/strong&gt; Allow users to filter queries by Act or category before searching.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Legal Case Retrieval:&lt;/strong&gt; Expand indexing to cover legal precedents and court cases.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Multilingual support:&lt;/strong&gt; Support query translation for regional languages.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Best Use of Google AI
&lt;/h3&gt;

&lt;p&gt;LawDecoder integrates the Google Gemini API (&lt;code&gt;gemini-3.5-flash&lt;/code&gt; or custom models) for generating context-grounded, empathetic, and structured legal advice derived from the hybrid SQLite-FTS5 retrieval output.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>ai</category>
      <category>sqlite</category>
    </item>
    <item>
      <title>I Rebuilt My AI Legal Assistant After Learning Why Vector-Only RAG Wasn't Enough</title>
      <dc:creator>Ishwar</dc:creator>
      <pubDate>Sat, 11 Jul 2026 11:11:49 +0000</pubDate>
      <link>https://dev.to/ishwar170695/i-rebuilt-my-ai-legal-assistant-after-learning-why-vector-only-rag-wasnt-enough-290i</link>
      <guid>https://dev.to/ishwar170695/i-rebuilt-my-ai-legal-assistant-after-learning-why-vector-only-rag-wasnt-enough-290i</guid>
      <description>&lt;p&gt;I built a legal AI assistant last year.&lt;/p&gt;

&lt;p&gt;One day I asked it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Someone forged my signature."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first law it retrieved wasn't about forgery.&lt;/p&gt;

&lt;p&gt;It was about counterfeit coins.&lt;/p&gt;

&lt;p&gt;That was the moment I realized the problem wasn't the LLM.&lt;/p&gt;

&lt;p&gt;It was my retrieval pipeline.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The surprising part:&lt;/strong&gt; I didn't change the LLM.&lt;/p&gt;

&lt;p&gt;Nearly all of the improvement came from redesigning the retrieval pipeline.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the story of how I debugged my search system, why dense vector search alone falls apart on domain-specific datasets, and how I redesigned the retrieval pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Failure
&lt;/h2&gt;

&lt;p&gt;To understand why the system was failing, we need to look at the mismatch between what was queried and what was actually retrieved in the original vector-only (v1) search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query:
"Someone forged my signature"

Top result (v1):
❌ BNS Section 180 — Possession of counterfeit coin

Expected:
✅ BNS Section 336 — Forgery
✅ BNS Section 340 — Using forged document
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Tracing the Cause
&lt;/h2&gt;

&lt;p&gt;I started tracing the retrieval pipeline step by step to understand why the wrong statutes were consistently ranking first.&lt;/p&gt;

&lt;p&gt;The embedding model wasn't "wrong." It was doing exactly what it was trained to do: place semantically similar concepts close together in vector space.&lt;/p&gt;

&lt;p&gt;Unfortunately, in legal search, &lt;em&gt;semantically similar&lt;/em&gt; isn't the same as &lt;em&gt;legally relevant&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Because "forgery" and "counterfeit" ended up close in embedding space, the retriever ranked counterfeit coin and government stamp provisions above the actual definition of document forgery. The retriever generalized too aggressively. It missed the specific statutory definition of forgery (&lt;code&gt;BNS Section 336&lt;/code&gt;) because the word "signature" was semantically distant from generic statutory descriptions of the offence.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Secondary Issues: RAM Bloat &amp;amp; Duplicates
&lt;/h3&gt;

&lt;p&gt;In addition to generalising incorrectly, caching large raw text strings (text, titles, act names) in a JavaScript array caused the Node.js process to consume over &lt;strong&gt;320 MB of RAM&lt;/strong&gt; at startup. Furthermore, since legal codes are highly repetitive, the search regularly returned duplicate entries of identical sections across different personal laws, cluttering the LLM's context window.&lt;/p&gt;




&lt;h2&gt;
  
  
  Redesigning the Retrieval Pipeline
&lt;/h2&gt;

&lt;p&gt;I realized that to make the assistant trustworthy, the real problem was search quality, not LLM capability. I redesigned the retrieval pipeline into a structured, hybrid search engine.&lt;/p&gt;

&lt;p&gt;![LawDecoder hybrid retrieval architecture diagram: SQLite FTS5 sparse keyword index and dense vector ONNX pipeline merged via Reciprocal Rank Fusion (RRF) and Domain Reranker]&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4pq8u6d4gs5wy888dwn1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4pq8u6d4gs5wy888dwn1.png" alt="Architecture" width="800" height="1422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The LLM remained almost unchanged. Nearly all improvements came from redesigning retrieval.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: SQLite + FTS5
&lt;/h3&gt;

&lt;p&gt;I moved all 4,892 legal sections out of JSON files and persisted them in a local &lt;strong&gt;SQLite&lt;/strong&gt; database. I created a virtual table using the &lt;strong&gt;FTS5 extension&lt;/strong&gt; to index all chapters, titles, and text contents. &lt;/p&gt;

&lt;p&gt;Now, exact terms are queried using a sparse keyword index (ranked via BM25), ensuring that queries containing specific statutory terms match their target immediately.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Lightweight Vector Cache
&lt;/h3&gt;

&lt;p&gt;I stripped all text metadata from Node.js memory. The startup script now loads only the &lt;code&gt;id&lt;/code&gt; (string) and the coordinate list—pre-processed into a compact &lt;code&gt;Float32Array&lt;/code&gt; object—into RAM. &lt;/p&gt;

&lt;p&gt;The actual text content remains on disk in SQLite and is only hydrated for the top 5 matched sections. This dropped the memory footprint by &lt;strong&gt;85%&lt;/strong&gt; (from 320 MB to &lt;strong&gt;48 MB&lt;/strong&gt;).&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3: Reciprocal Rank Fusion (RRF)
&lt;/h3&gt;

&lt;p&gt;Instead of relying on either vector search or keyword search, I fused them. The backend runs both searches, takes the top 50 matches from each, and combines their rankings using standard &lt;strong&gt;Reciprocal Rank Fusion (RRF)&lt;/strong&gt;. RRF rewards documents that rank highly in both methods without needing to normalize scores between sparse BM25 and dense cosine models.&lt;/p&gt;

&lt;p&gt;Here is the core JS implementation of the RRF merge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rrfScores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Standard constant for RRF&lt;/span&gt;

&lt;span class="c1"&gt;// Process vector ranking positions (dense)&lt;/span&gt;
&lt;span class="nx"&gt;vectorRankings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rrfScores&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="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Process FTS5 keyword ranking positions (sparse) and add to scores&lt;/span&gt;
&lt;span class="nx"&gt;ftsRankings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rrfScores&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="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;rrfScores&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="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;existingScore&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Sort matched IDs based on fused RRF score&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mergedRanking&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rrfScores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Top 20 candidates for reranking&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Domain Reranker (Deterministic Guardrail)
&lt;/h3&gt;

&lt;p&gt;To resolve the counterfeit coin noise without running a heavy, slow transformer cross-encoder, I built a lightweight, deterministic &lt;strong&gt;Domain Reranker&lt;/strong&gt; in JavaScript. This is a deterministic rule-based reranker tailored to the legal domain—not a learned neural cross-encoder. &lt;/p&gt;

&lt;p&gt;It loads the top 20 candidates returned by the RRF step and checks for specific intent signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  If the query is document/signature forgery-related, it checks if a retrieved document is a coin or banknote counterfeit section. If yes, it &lt;strong&gt;penalizes the score by 99%&lt;/strong&gt; (&lt;code&gt;* 0.01&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  It &lt;strong&gt;boosts direct document forgery offences&lt;/strong&gt; (containing &lt;code&gt;"forgery"&lt;/code&gt; or &lt;code&gt;"forged"&lt;/code&gt;) by &lt;strong&gt;300%&lt;/strong&gt; (&lt;code&gt;* 3.0&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  It filters out duplicate sections using content snippet prefixes.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Heuristic Domain Reranking&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isDocumentForgeryRelated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isCoinOrStampOrCurrency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
    &lt;span class="nx"&gt;titleLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;coin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;titleLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stamp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; 
    &lt;span class="nx"&gt;titleLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;currency&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;titleLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bank-note&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;contentLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;coin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;contentLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stamp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; 
    &lt;span class="nx"&gt;contentLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;currency-note&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isCoinOrStampOrCurrency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;adjustedScore&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// heavily penalize counterfeit coin/stamps (reduce by 99%)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;titleLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;forgery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;titleLower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;forged&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;adjustedScore&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// strong boost for direct forgery definitions/offences&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Performance &amp;amp; Evaluation
&lt;/h2&gt;

&lt;p&gt;To evaluate the redesign, I assembled a benchmark of 100 manually verified legal queries spanning criminal law, cybercrime, family law, consumer protection, and procedural law.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;v1 (Naive Vector RAG)&lt;/th&gt;
&lt;th&gt;v2.1 (Hybrid Search - Current)&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Search Engine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dense Vector (Linear JSON scan)&lt;/td&gt;
&lt;td&gt;Hybrid (SQLite FTS5 + Dense Vector + RRF + Reranker)&lt;/td&gt;
&lt;td&gt;Major retrieval precision upgrade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Avg. Query Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;466 ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12 ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;97.4% speedup&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory Cache Footprint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~320 MB&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~48 MB&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85.0% RAM savings&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Duplicate Citations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Present (up to 40% overlaps)&lt;/td&gt;
&lt;td&gt;Deduplicated (0% overlaps)&lt;/td&gt;
&lt;td&gt;Verified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Top-5 Relevant Retrieval Rate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~68%&lt;/td&gt;
&lt;td&gt;~91%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+23% accuracy gain&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Latency is based on 100 benchmark queries. Memory is process-level heap size at startup. Accuracy is evaluated on top-5 target matches using a manually verified benchmark dataset of 100 queries.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;None of these improvements required changing the language model. The gains came almost entirely from retrieval engineering. For this benchmark query, the top retrieved references aligned with the expected legal provisions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;BNS 340:&lt;/strong&gt; Forged document and using it as genuine&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;BNS 336:&lt;/strong&gt; Forgery definition and penalty&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;BNS 339:&lt;/strong&gt; Possession of forged document&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;BNS 335:&lt;/strong&gt; Making a false document&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Evidence Act Section 65:&lt;/strong&gt; Proof of signature and handwriting&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Production Walkthrough
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1️⃣ User Chat Interface
&lt;/h3&gt;

&lt;p&gt;Clean, legal explanation interface for end users:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9galq6jybdykddw7o6rh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9galq6jybdykddw7o6rh.png" alt="LawDecoder Streamlit user chat landing page showing response layout" width="800" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2️⃣ Structured Offence &amp;amp; Citation Details
&lt;/h3&gt;

&lt;p&gt;Deduplicated citations with developer metrics visible in Developer Mode:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fomxclvmr5iw3dsnkgwn1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fomxclvmr5iw3dsnkgwn1.png" alt="LawDecoder citation view in developer mode displaying RRF ranks and selection reasons" width="800" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3️⃣ System Evaluation Dashboard
&lt;/h3&gt;

&lt;p&gt;Performance comparisons and technical architecture story:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmuyusdsz4zxf0fq1vd4c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmuyusdsz4zxf0fq1vd4c.png" alt="LawDecoder developer dashboard showing performance latency and accuracy benchmarks comparison table" width="800" height="582"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Retrieval quality sets the upper bound for RAG quality.&lt;/li&gt;
&lt;li&gt;Dense embeddings alone are rarely enough for domain-specific search.&lt;/li&gt;
&lt;li&gt;SQLite + FTS5 can be an excellent retrieval engine for small-to-medium corpora.&lt;/li&gt;
&lt;li&gt;Simple deterministic rerankers can eliminate domain-specific retrieval errors without requiring another neural model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If I continue evolving this project, the next improvements I'd explore are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Cross-encoder reranking:&lt;/strong&gt; Integrate lightweight cross-encoders (e.g. BGE reranker) for advanced ranking.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Metadata-aware retrieval:&lt;/strong&gt; Allow users to filter queries by Act or category before searching.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Legal Case Retrieval:&lt;/strong&gt; Expand indexing to cover legal precedents and court cases in addition to statutory acts.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Multilingual support:&lt;/strong&gt; Support query translation for regional languages.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Going into this project, I assumed improving the LLM would improve the assistant.&lt;/p&gt;

&lt;p&gt;Instead, I learned that retrieval quality determines the ceiling of any RAG system.&lt;/p&gt;

&lt;p&gt;The LLM can only reason over what you retrieve.&lt;/p&gt;

&lt;p&gt;Improving retrieval turned out to be far more impactful than switching models.&lt;/p&gt;

&lt;p&gt;If you're building domain-specific AI—whether for legal, medical, or enterprise search—I'd recommend spending as much time on retrieval engineering as prompt engineering.&lt;/p&gt;




&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;The complete implementation—including SQLite ingestion, FTS5 indexing, Reciprocal Rank Fusion, evaluation queries, and benchmark samples—is available on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/ishwar170695/LawDecoder" rel="noopener noreferrer"&gt;https://github.com/ishwar170695/LawDecoder&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>backend</category>
      <category>sqlite</category>
      <category>rag</category>
    </item>
    <item>
      <title>I Audited $753 of Coding-Agent Usage. I Found 94.5% Context Reuse.</title>
      <dc:creator>Ishwar</dc:creator>
      <pubDate>Fri, 19 Jun 2026 11:10:25 +0000</pubDate>
      <link>https://dev.to/ishwar170695/i-audited-753-of-coding-agent-usage-i-found-945-context-reuse-51ha</link>
      <guid>https://dev.to/ishwar170695/i-audited-753-of-coding-agent-usage-i-found-945-context-reuse-51ha</guid>
      <description>&lt;p&gt;I expected prompt caching to be one of the biggest cost optimizations for coding agents.&lt;/p&gt;

&lt;p&gt;After all, every request carries system prompts, tool definitions, and instructions that rarely change. Caching those felt like free money.&lt;/p&gt;

&lt;p&gt;I also expected a second lever: unused retrieved context. Agents constantly read files, fetch logs, inspect directories, and explore codebases. Surely a meaningful fraction of that context never actually influences the final output.&lt;/p&gt;

&lt;p&gt;So I built a small tool, &lt;strong&gt;context-audit&lt;/strong&gt;, and ran it across 27 real coding-agent sessions representing &lt;strong&gt;$753.24 of input spend&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Both assumptions didn't survive the benchmark.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Measured
&lt;/h2&gt;

&lt;p&gt;Across 27 sessions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Context Reuse&lt;/td&gt;
&lt;td&gt;94.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Novel Context&lt;/td&gt;
&lt;td&gt;5.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt Cache Savings&lt;/td&gt;
&lt;td&gt;1.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unused Context Cost&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpwda0tubup1761pof2u4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpwda0tubup1761pof2u4.png" alt="token expenditure" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Prompt caching accounted for only &lt;strong&gt;1.0%&lt;/strong&gt; of potential savings.&lt;/p&gt;

&lt;p&gt;Unused retrieved context accounted for just &lt;strong&gt;0.4%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Meanwhile, &lt;strong&gt;94.5% of all context tokens were repeated content&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The dominant cost wasn't unused retrieval.&lt;/p&gt;

&lt;p&gt;It wasn't static prompts.&lt;/p&gt;

&lt;p&gt;It was accumulated conversation history.&lt;/p&gt;

&lt;p&gt;The same information kept appearing again and again as sessions grew longer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger the Session, the More Repetitive It Became
&lt;/h2&gt;

&lt;p&gt;Context reuse increased sharply with session size:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Final Context Size&lt;/th&gt;
&lt;th&gt;Avg Reuse&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt; 5k tokens&lt;/td&gt;
&lt;td&gt;66.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5k–20k tokens&lt;/td&gt;
&lt;td&gt;92.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20k–50k tokens&lt;/td&gt;
&lt;td&gt;96.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;gt; 50k tokens&lt;/td&gt;
&lt;td&gt;99.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The longer a session ran, the more repetitive it became.&lt;/p&gt;

&lt;p&gt;That was not what I expected to find.&lt;/p&gt;

&lt;p&gt;My optimization instincts were pointing at the wrong bottleneck.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Insight: Coding Agents Have Two Memory Systems
&lt;/h2&gt;

&lt;p&gt;While digging through transcripts, I started thinking about coding agents differently from chatbots.&lt;/p&gt;

&lt;p&gt;They appear to operate with two distinct memory systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workspace Memory
&lt;/h3&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Files&lt;/li&gt;
&lt;li&gt;Logs&lt;/li&gt;
&lt;li&gt;Terminal outputs&lt;/li&gt;
&lt;li&gt;Build artifacts&lt;/li&gt;
&lt;li&gt;Compiler errors&lt;/li&gt;
&lt;li&gt;Directory structures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This information often exists on disk.&lt;/p&gt;

&lt;p&gt;The agent can frequently reconstruct it by reading the workspace again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conversational Memory
&lt;/h3&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User preferences&lt;/li&gt;
&lt;li&gt;Design decisions&lt;/li&gt;
&lt;li&gt;Rejected ideas&lt;/li&gt;
&lt;li&gt;Constraints&lt;/li&gt;
&lt;li&gt;Trade-offs&lt;/li&gt;
&lt;li&gt;Architectural rationale&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This information exists only inside the conversation.&lt;/p&gt;

&lt;p&gt;Once it's removed, it may be gone for good.&lt;/p&gt;

&lt;p&gt;That distinction changed how I think about context management.&lt;/p&gt;

&lt;p&gt;Not all context is equally disposable.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Pruning Failure Mode
&lt;/h2&gt;

&lt;p&gt;Picture a long coding session.&lt;/p&gt;

&lt;p&gt;Early in the conversation, the user decides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No embeddings&lt;/li&gt;
&lt;li&gt;No LLM-as-a-judge&lt;/li&gt;
&lt;li&gt;No HTML dashboards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The team discusses alternatives and agrees on a simpler approach.&lt;/p&gt;

&lt;p&gt;Fifty turns later, those decisions may no longer exist anywhere except the conversation history.&lt;/p&gt;

&lt;p&gt;The workspace still contains the code.&lt;/p&gt;

&lt;p&gt;But it doesn't contain every rejected path.&lt;/p&gt;

&lt;p&gt;A naive pruning strategy removes what appears to be old conversation noise.&lt;/p&gt;

&lt;p&gt;Unfortunately, it may also remove the reasoning behind the project.&lt;/p&gt;

&lt;p&gt;The result is an agent that suddenly starts recommending ideas the user explicitly rejected earlier.&lt;/p&gt;

&lt;p&gt;Concretely, a summarizer that compresses primarily by recency may preserve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the latest file tree,&lt;/li&gt;
&lt;li&gt;recent command outputs,&lt;/li&gt;
&lt;li&gt;recent compiler logs,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;while dropping a critical design decision made dozens of turns earlier.&lt;/p&gt;

&lt;p&gt;The expensive-looking context survives.&lt;/p&gt;

&lt;p&gt;The cheap-looking context that actually mattered disappears.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;If you're manually pruning, summarizing, or compressing long-running coding-agent sessions, you may be deleting the rationale behind decisions rather than the expensive parts of the context.&lt;/p&gt;

&lt;p&gt;Workspace state is often reconstructable.&lt;/p&gt;

&lt;p&gt;Conversational decisions frequently are not.&lt;/p&gt;

&lt;p&gt;That doesn't mean pruning is wrong.&lt;/p&gt;

&lt;p&gt;It means pruning needs to distinguish between:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Technical execution history that can be recovered from the workspace.&lt;/li&gt;
&lt;li&gt;Alignment history that only exists in the conversation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Treating both categories the same can produce subtle regressions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Doesn't Prove
&lt;/h2&gt;

&lt;p&gt;This isn't a universal law.&lt;/p&gt;

&lt;p&gt;Twenty-seven sessions are enough for an interesting observation, not enough to claim every coding agent behaves this way.&lt;/p&gt;

&lt;p&gt;The benchmark covers coding-agent workflows with disk-backed state.&lt;/p&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RAG systems&lt;/li&gt;
&lt;li&gt;General chatbots&lt;/li&gt;
&lt;li&gt;Research agents&lt;/li&gt;
&lt;li&gt;Customer support agents&lt;/li&gt;
&lt;li&gt;Other non-coding workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The findings should be interpreted within that scope.&lt;/p&gt;

&lt;p&gt;But they were enough to overturn my expectations.&lt;/p&gt;

&lt;p&gt;I started this project expecting prompt caching and retrieval waste to dominate.&lt;/p&gt;

&lt;p&gt;In this dataset, they barely moved the needle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Audit a single transcript&lt;/span&gt;
context-audit run transcript.jsonl

&lt;span class="c"&gt;# Benchmark an entire directory&lt;/span&gt;
context-audit benchmark ~/.claude/projects
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool reports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Context reuse ratios&lt;/li&gt;
&lt;li&gt;Estimated costs&lt;/li&gt;
&lt;li&gt;Repeated blocks&lt;/li&gt;
&lt;li&gt;Context growth patterns&lt;/li&gt;
&lt;li&gt;Potential caching savings&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;GitHub: &lt;a href="//github.com/ishwar170695/context-audit"&gt;context-audit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The benchmark changed how I think about coding-agent memory.&lt;/p&gt;

&lt;p&gt;I started this project looking for waste in static prompts and retrieval.&lt;/p&gt;

&lt;p&gt;Instead, I found a system spending most of its context budget carrying forward its own history.&lt;/p&gt;

&lt;p&gt;If you're running long Claude Code, Cursor, Aider, or similar coding-agent workflows, I'd love to know whether you're seeing the same pattern.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>agents</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Why your devcontainer fails on corporate networks (and how to fix it)</title>
      <dc:creator>Ishwar</dc:creator>
      <pubDate>Sun, 31 May 2026 09:06:56 +0000</pubDate>
      <link>https://dev.to/ishwar170695/why-your-devcontainer-fails-on-corporate-networks-and-how-to-fix-it-45dh</link>
      <guid>https://dev.to/ishwar170695/why-your-devcontainer-fails-on-corporate-networks-and-how-to-fix-it-45dh</guid>
      <description>&lt;p&gt;You set up a devcontainer, try to run &lt;code&gt;npm install&lt;/code&gt; or &lt;code&gt;pip install&lt;/code&gt;, and it just fails. SSL error. Certificate verify failed. You Google it for an hour and find nothing useful. If you're on a corporate network, this is almost certainly your company's proxy intercepting HTTPS traffic with its own certificate and your container has no idea that cert exists.&lt;/p&gt;

&lt;p&gt;Your host machine trusts that proxy cert because IT installed it in your OS cert store. But your devcontainer is a fresh Linux environment. It doesn't inherit anything from your host. So every HTTPS request your tools make inside the container fails verification.&lt;/p&gt;

&lt;p&gt;I kept seeing this problem come up in devcontainer issues and Discord threads with no clean fix. Every solution involved editing Dockerfiles or committing certs to repos. &lt;/p&gt;

&lt;p&gt;So I built CertSync to handle it properly. It scans your host cert store, detects corporate/MITM certs automatically, and injects them into your devcontainer, no Dockerfile changes, no committing certs to your repo. One command and your container trusts the same roots your host does.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ishwar170695/certsync" rel="noopener noreferrer"&gt;github.com/ishwar170695/certsync&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devcontainers</category>
      <category>devops</category>
      <category>go</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
