<?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: Raj Kumar</title>
    <description>The latest articles on DEV Community by Raj Kumar (@techie_raj).</description>
    <link>https://dev.to/techie_raj</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%2F3907966%2F21f1d29f-e1be-43b5-bb37-cd07430f828c.png</url>
      <title>DEV Community: Raj Kumar</title>
      <link>https://dev.to/techie_raj</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/techie_raj"/>
    <language>en</language>
    <item>
      <title>RAG Is Dead. Who Killed It?</title>
      <dc:creator>Raj Kumar</dc:creator>
      <pubDate>Tue, 21 Jul 2026 12:45:45 +0000</pubDate>
      <link>https://dev.to/techie_raj/rag-is-dead-who-killed-it-5g8c</link>
      <guid>https://dev.to/techie_raj/rag-is-dead-who-killed-it-5g8c</guid>
      <description>&lt;p&gt;&lt;em&gt;Your RAG pipeline just returned the &lt;strong&gt;wrong answer&lt;/strong&gt;. &lt;strong&gt;Again&lt;/strong&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The query was simple. The doc had the answer. You watched the top-5 chunks come back and none of them held it. The right paragraph sat two pages away from anything your embedding model thought was "similar." So the LLM hallucinated something plausible, your user trusted it, and you spent the afternoon tuning &lt;code&gt;chunk_size&lt;/code&gt; from 512 to 1024 and back.&lt;/p&gt;

&lt;p&gt;This is not a tuning problem. This is the architecture.&lt;/p&gt;

&lt;p&gt;Vector RAG was a clever hack for a 2022 problem: LLMs had tiny context windows, so we shredded documents into chunks, embedded them, and prayed cosine similarity would find the right shred. It worked well enough on FAQ pages and Notion dumps that we called it a pattern and shipped it into production at every company on earth.&lt;/p&gt;

&lt;p&gt;Then we pointed it at a 200-page 10-K. A Kubernetes operator manual. A pharma SOP. A legal contract. And it fell apart.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Chunking Lie
&lt;/h2&gt;

&lt;p&gt;Vector RAG rests on four assumptions. All four break on real documents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assumption 1: Similarity equals relevance.&lt;/strong&gt;&lt;br&gt;
It doesn't. &lt;code&gt;"revenue declined"&lt;/code&gt; and &lt;code&gt;"revenue grew"&lt;/code&gt; sit next to each other in embedding space — opposite meanings, near-identical vectors. Domain jargon drifts even worse. &lt;code&gt;"margin"&lt;/code&gt; means one thing in finance, another in typography, another in ML. Your general-purpose embedding model doesn't know which doc it's in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assumption 2: The right answer fits in k chunks.&lt;/strong&gt;&lt;br&gt;
Why is k=5? Because someone's tutorial said so. Real answers span sections, reference earlier definitions, sit inside tables that got shredded on ingest. Top-k is a guess dressed up as a hyperparameter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assumption 3: Chunk boundaries preserve meaning.&lt;/strong&gt;&lt;br&gt;
They don't. A 512-token window slices mid-sentence, mid-table, mid-recipe. The chunk that contains the answer loses the heading that gives it context. The LLM sees &lt;code&gt;"...bake at 425°F for 40 minutes"&lt;/code&gt; and has no idea which dish "it" belongs to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assumption 4: Documents are bags of text.&lt;/strong&gt;&lt;br&gt;
They aren't. A 10-K has a structure. A manual has chapters. A contract has clauses that reference other clauses. Vector RAG throws all of that away on ingest, then acts surprised when retrieval can't find its way back.&lt;/p&gt;

&lt;p&gt;Every RAG failure I've debugged in the last two years traces back to one of these four. Bigger embedding models don't fix them. Reranking papers over them. Hybrid BM25 just postpones them. The problem isn't the retriever — it's the premise.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Humans don't read a 200-page doc by cosine similarity. They open the table of contents.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How You Actually Find an Answer
&lt;/h2&gt;

&lt;p&gt;Forget databases for a second. Picture a 400-page cookbook, and you want the roast chicken recipe.&lt;/p&gt;

&lt;p&gt;You don't read all 400 pages looking for the passage most "similar" to &lt;em&gt;chicken&lt;/em&gt;. You flip to the contents, find &lt;strong&gt;Poultry&lt;/strong&gt;, turn to that chapter, run your finger down to &lt;strong&gt;Roast Chicken&lt;/strong&gt;, and read the whole recipe — ingredients, oven temp, and timing all together, in order.&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%2F1dlyvb5rm6jrmamcpgw8.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%2F1dlyvb5rm6jrmamcpgw8.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now imagine doing it the vector-RAG way: tear every page out, shuffle them into a bin, and pull the five loose pages whose words look closest to your question. You might grab the ingredients list but miss the oven temperature on the next page. You might grab a chicken &lt;em&gt;stock&lt;/em&gt; recipe by mistake. And you'd have no idea which chapter any page came from.&lt;/p&gt;

&lt;p&gt;That's the difference. Humans use &lt;strong&gt;reasoning over structure&lt;/strong&gt;; vector RAG uses &lt;strong&gt;similarity over shredded text&lt;/strong&gt;. The table of contents isn't decoration — it's a map of meaning the author already drew for you, and chunking throws it in the trash.&lt;/p&gt;

&lt;p&gt;So what if retrieval kept the map?&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet the Killer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It's called PageIndex.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of embedding chunks, PageIndex builds a &lt;strong&gt;tree&lt;/strong&gt; from the document's structure — chapters, sections, sub-sections — mirroring the table of contents. (No table of contents? An LLM generates the tree from the page text.) It keeps the cookbook intact instead of shredding it. Each node knows what it contains. No vectors. No cosine similarity. No &lt;code&gt;k&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At query time, an LLM walks the tree the way a human walks a TOC. It starts at the root, reasons about which branch is most likely to hold the answer, descends, re-evaluates, and either drills deeper or backs out. When it lands on the right node, it reads the full, intact section — headers, tables, and context still attached.&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%2Fuihc3eujt1q81tml8hhv.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%2Fuihc3eujt1q81tml8hhv.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two things fall out of this for free:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Context stays whole.&lt;/strong&gt; The answer arrives with the section that frames it. That orphaned &lt;code&gt;"...bake at 425°F for 40 minutes"&lt;/code&gt; from earlier now carries its heading — &lt;em&gt;Roast Chicken&lt;/em&gt; — instead of floating free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Retrieval is explainable.&lt;/strong&gt; You get the reasoning trace — the exact path from root down to the answer node. When it's wrong, you see &lt;em&gt;where&lt;/em&gt; it turned wrong. Try debugging a cosine score.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it actually work?
&lt;/h3&gt;

&lt;p&gt;PageIndex is built and open-sourced by &lt;a href="https://github.com/VectifyAI/PageIndex" rel="noopener noreferrer"&gt;Vectify AI&lt;/a&gt; (&lt;a href="https://docs.pageindex.ai/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;). And the numbers on structured docs are hard to argue with. On &lt;strong&gt;FinanceBench&lt;/strong&gt; — QA over real financial filings — Vectify's &lt;a href="https://github.com/VectifyAI/Mafin2.5-FinanceBench" rel="noopener noreferrer"&gt;Mafin 2.5&lt;/a&gt;, built on PageIndex, reports:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;98.7%&lt;/strong&gt; accuracy — vs &lt;strong&gt;31%&lt;/strong&gt; for ChatGPT-4o with search, and &lt;strong&gt;45%&lt;/strong&gt; for Perplexity, on the same documents.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Worth being honest: that's a vendor-reported number, not an independent replication. But the pattern holds — structured documents are exactly where similarity search hurts most, and where a tree wins biggest.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Vector RAG searches for text that looks like the answer. PageIndex reasons about where the answer lives.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Same Question, Same Doc
&lt;/h2&gt;

&lt;p&gt;Enough theory. Back to that 400-page cookbook, one real query: &lt;em&gt;"What temperature do I roast the chicken at, and for how long?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vector RAG&lt;/strong&gt; returns the top-5 chunks by similarity. Four are about chicken &lt;em&gt;stock&lt;/em&gt;, chicken &lt;em&gt;salad&lt;/em&gt;, chicken &lt;em&gt;stir-fry&lt;/em&gt; — they all say "chicken." One catches &lt;code&gt;"425°F for 40 minutes"&lt;/code&gt; but lost its heading on chunking, so the model can't tell if that's the roast chicken or the roast &lt;em&gt;potatoes&lt;/em&gt; two pages over. Answer: confident, wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PageIndex&lt;/strong&gt; walks the tree — root → &lt;em&gt;Poultry&lt;/em&gt; → &lt;em&gt;Roast Chicken&lt;/em&gt; → the intact recipe, temperature and timing under their own heading, ingredients and steps still attached. It reads the answer in context and gets it right.&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%2Fax8kirtn0i1sc9bl67dr.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%2Fax8kirtn0i1sc9bl67dr.png" alt=" " width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same book. One retriever guessed at surface text; the other navigated to where the answer actually lived — the same way you would.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Part Everyone Misses: Cross-References
&lt;/h2&gt;

&lt;p&gt;Real documents are full of pointers. "Prepare the brine as described on page 12." "See the pan-searing technique in Chapter 2." "Use the spice blend from the recipe above." The roast chicken recipe only makes sense alongside a brine three chapters back.&lt;/p&gt;

&lt;p&gt;Vector RAG is structurally blind to this. It fetches the top-k chunks that &lt;em&gt;look&lt;/em&gt; like your query — and a cross-reference looks like nothing. The chunk that says "brine the bird first" scores well; the chunk three chapters away that actually &lt;em&gt;explains how to make the brine&lt;/em&gt; doesn't, so it never comes back. The model answers with half the recipe and no idea the other half exists.&lt;/p&gt;

&lt;p&gt;PageIndex sees the whole tree at once. When the node it lands on points elsewhere — "see the brine on page 12" — the navigating LLM can follow that pointer and pull the referenced node too. It isn't a dedicated link-resolver; it's a side effect of reasoning over the full structure instead of a bag of disconnected fragments. But the result is what matters: the dependency gets read, not dropped.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A chunk is an island. A tree node knows its neighbors.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  But I Have a Whole Shelf, Not One Book
&lt;/h2&gt;

&lt;p&gt;Fair — real systems aren't one cookbook, they're a library of thousands. The instinct is to reach for vectors again here: embed every book, do a similarity search to pick candidates, then navigate inside. And you &lt;em&gt;can&lt;/em&gt; — a vector first pass narrows the shelf, then PageIndex walks each book to the exact recipe. It's fast, and it's the current default in Vectify's own API, mostly because walking a giant tree with an LLM costs more calls than a millisecond vector lookup.&lt;/p&gt;

&lt;p&gt;But notice what just happened: you dragged the shredder back into your pipeline for the &lt;em&gt;one job&lt;/em&gt; it was never bad at — a coarse "which books are roughly about Italian food?" And now you're maintaining an embedding store, a vector index, and a reasoning layer to answer one question.&lt;/p&gt;

&lt;p&gt;The cleaner idea is to go &lt;strong&gt;structure all the way up&lt;/strong&gt;: the whole shelf becomes one big tree. Cuisine → book → chapter → recipe, one hierarchy the LLM navigates top-down — no embeddings, no separate vector store, no re-indexing when documents change. Vectify ships this as their "file system" layer for corpus-scale search. It's the newer path and still maturing, but it's the honest end state of the argument. If structure beats similarity &lt;em&gt;inside&lt;/em&gt; a document, it beats it &lt;em&gt;across&lt;/em&gt; a collection too.&lt;/p&gt;

&lt;p&gt;The lesson isn't "add vectors for scale." It's &lt;strong&gt;don't reach for a similarity index at every level as a reflex&lt;/strong&gt; — that's overhead masquerading as architecture. Use it as a temporary speed shortcut if you must, and drop it the moment structure can do the job. Either way, the shredder never comes back.&lt;/p&gt;




&lt;h2&gt;
  
  
  Which One, When
&lt;/h2&gt;

&lt;p&gt;PageIndex is not free. A vector lookup is ~50ms and costs cents; walking a tree with an LLM takes seconds — sometimes tens of seconds — and multiple model calls. You're trading milliseconds and cents for accuracy, whole-context answers, and an audit trail. Whether that's worth it is entirely about your workload:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;Reach for&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dense filings, contracts, SOPs, manuals&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PageIndex&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;real structure to navigate; answers need whole-section context + an audit trail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Steps or clauses that reference other steps&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PageIndex&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;follows cross-references vector search is blind to — the difference between a right answer and a confidently wrong one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A million structured docs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PageIndex&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;one hierarchy, navigated top-down — vectors optional as a speed prefilter, not a requirement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100k tickets, FAQs, chat logs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Vector RAG&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;genuinely flat — no structure to navigate, sub-second latency, cents per query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time chatbot autocomplete&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Vector RAG&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~50ms lookup; a tree-walk is far too slow&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Vectors still win where the data is genuinely flat and latency is everything — chat logs, FAQs, autocomplete. But the moment your documents have structure, that's PageIndex's turf, one book or a million.&lt;/p&gt;




&lt;h2&gt;
  
  
  So Is RAG Actually Dead?
&lt;/h2&gt;

&lt;p&gt;What's dead is the reflex to reach for &lt;strong&gt;vector-only RAG as the default for every retrieval problem&lt;/strong&gt;. That default was never right for structured documents; we just didn't have a better option worth the effort. Now we do.&lt;/p&gt;

&lt;p&gt;Retrieval is turning from a similarity-search problem into a reasoning problem. Vectors were the hack we needed when models were small and context was scarce. Neither is true anymore.&lt;/p&gt;

&lt;p&gt;Point your worst-performing RAG query — the one you've been re-chunking for a week — at a tree instead. The code is open source: &lt;a href="https://github.com/VectifyAI/PageIndex" rel="noopener noreferrer"&gt;github.com/VectifyAI/PageIndex&lt;/a&gt;. Then tell me what breaks. &lt;strong&gt;Change my mind: what breaks PageIndex?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're building at the seam between real apps and LLMs, that's most of what I write about here — follow along if it's your kind of problem.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://www.linkedin.com/in/rajkumar7" rel="noopener noreferrer"&gt;Raj Kumar&lt;/a&gt; is a Toronto-based tech lead. Building software since 2011, AI since 2014 (before the buzzword). Ruby, Python, and system architecture; currently working the seam between production apps and LLM agents.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>llm</category>
      <category>retrieval</category>
    </item>
    <item>
      <title>Your Rails App Is Already AI-Ready. You Just Don't Know It.</title>
      <dc:creator>Raj Kumar</dc:creator>
      <pubDate>Tue, 05 May 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/techie_raj/your-rails-app-is-already-ai-ready-you-just-dont-know-it-1jc4</link>
      <guid>https://dev.to/techie_raj/your-rails-app-is-already-ai-ready-you-just-dont-know-it-1jc4</guid>
      <description>&lt;p&gt;I've spent the last several months watching a Claude-based agent read a production Rails codebase — opening files, tracing scopes, following background jobs, identifying the right place to investigate a bug I'd written years ago. The strange thing isn't that it works. &lt;strong&gt;It's how little I had to do to make it work.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I went in expecting to write a lot of glue. Entity maps. Canonical module summaries. Hand-built manifests telling the model where things live. I wrote none of it. The agent reads &lt;code&gt;config/routes.rb&lt;/code&gt;, follows the conventions Rails apps have agreed on for fifteen years, and finds what it needs.&lt;/p&gt;

&lt;p&gt;That disconnect is what this post is about. Not "AI for Rails." &lt;strong&gt;Rails, seen through an LLM's eyes.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The agent is a new hire who reads fast
&lt;/h2&gt;

&lt;p&gt;The mental model that helped me most is this: &lt;em&gt;the agent is a new hire on day one.&lt;/em&gt; It has never seen your code. It has no institutional memory. But it has general knowledge of Ruby and Rails, the way a new hire does. It reads quickly and forgets nothing — and it believes what it reads.&lt;/p&gt;

&lt;p&gt;Under that frame, every decision you've made about naming, layout, and structure is either cheap for the agent to navigate or expensive. And "cheap to navigate" almost always means &lt;strong&gt;"looks like every other codebase of this kind."&lt;/strong&gt; That's where Rails wins, and wins in a way that surprised me even after a decade and a half of writing it.&lt;/p&gt;

&lt;p&gt;Here are five things I kept noticing. None of them are features. All of them are conventions.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. &lt;code&gt;app/services/&lt;/code&gt; is a keyword, even to the model
&lt;/h2&gt;

&lt;p&gt;We work in regulated clinical software. Tickets often look like: &lt;em&gt;"Customer reports the nightly audit export for study X came back empty."&lt;/em&gt; An ordinary support engineer would know, without thinking, to start at &lt;code&gt;app/services/exports/&lt;/code&gt;. They don't know because someone documented it. They know because that's where Rails apps put services.&lt;/p&gt;

&lt;p&gt;The agent knows the same thing, for the same reason. It was trained on millions of Rails apps following the convention. Ask it where the logic for X lives; it opens &lt;code&gt;app/services/&lt;/code&gt; and is often right on the first read.&lt;/p&gt;

&lt;p&gt;Imagine the alternative. Seventeen repositories with seventeen philosophies — hexagonal architecture here, DDD bounded contexts there, &lt;code&gt;lib/&lt;/code&gt; somewhere else. All defensible. Taken together, each new ticket forces the agent to spend its first five minutes learning your idiosyncrasies before it can do any work. Those minutes are tokens. Tokens are budget. Budget is what decides whether a per-ticket analysis costs $0.30 or $5.&lt;/p&gt;

&lt;p&gt;Shared conventions compound. Clever per-repo conventions don't.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Active Record scopes are self-documenting in a way no repository pattern is
&lt;/h2&gt;

&lt;p&gt;Here's a scope close in shape to one that lives in our codebase, with domain nouns swapped:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:visible_to&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="n"&gt;sponsor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:study&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;studies: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;sponsor: &lt;/span&gt;&lt;span class="n"&gt;sponsor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;archived_at: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;}&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;p&gt;One line. It tells you: this model is tenant-scoped to a sponsor, through a study association, and archived studies don't count. The agent reads that scope and has absorbed three things it would otherwise need to discover by tracing across files: &lt;em&gt;tenancy rule, association path, lifecycle rule.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Compare to the equivalent in a codebase using a repository-pattern abstraction over an ORM — a &lt;code&gt;StudyRepository.find_visible_to(sponsor)&lt;/code&gt; method that delegates to a query builder that composes predicates defined somewhere else. Same behavior; three files to read; each file requires inference to connect to the next.&lt;/p&gt;

&lt;p&gt;Agents reason the way tired humans do: by pattern-matching against the nearest legible evidence. &lt;strong&gt;Active Record puts the legible evidence at the definition site.&lt;/strong&gt; The agent reads once and knows. No tracing, no inference, no token budget spent on reconstruction.&lt;/p&gt;

&lt;p&gt;This is why, when I watch the agent answer questions about multi-tenant access, it almost never gets it wrong. The tenancy rules are &lt;em&gt;inside the model, at the scope.&lt;/em&gt; It can't miss them.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Background jobs that name themselves
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;AuditTrailReplayJob.perform_later(study_id, since: 24.hours.ago)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Tell me what that does. You told me without reading the class.&lt;/p&gt;

&lt;p&gt;Now picture the same operation as a publish to a generic queue with a JSON payload and a routing consumer on the other end that dispatches based on an event type string. Same behavior. The agent has to find the producer, follow the payload, find the consumer, match the event type, read the handler. Four files, three inferences, and one place where a typo in the event type silently breaks everything.&lt;/p&gt;

&lt;p&gt;Sidekiq, GoodJob, Solid Queue — doesn't matter which — gave us a convention where background work has &lt;strong&gt;a name and a signature that describes itself.&lt;/strong&gt; For an agent trying to trace "what happens after a study closes?", that's an enormous gift. It finds the controller that receives the close action, sees three &lt;code&gt;Job.perform_later&lt;/code&gt; calls with self-describing names, and is done. It doesn't spelunk through an event bus.&lt;/p&gt;

&lt;p&gt;I've watched this specifically make the difference on root-cause analysis. The agent's reasoning chains are shorter. Shorter chains mean fewer hallucination opportunities. Fewer hallucinations mean I trust the output.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Release branches are how Rails ships, and that matters more than you think
&lt;/h2&gt;

&lt;p&gt;Our platform ships on a quarterly cadence. Customers run a pinned version — let's call it 26.1.0 or 26.2.0. Our agent's first concrete task on any new ticket isn't to analyze the error. &lt;strong&gt;It's to resolve which branch the customer is actually running, and to read that branch.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If it reads &lt;code&gt;main&lt;/code&gt;, every downstream conclusion is drifting. "This function handles X" — yes, on &lt;code&gt;main&lt;/code&gt;, not on the release the customer is on.&lt;/p&gt;

&lt;p&gt;This sounds like an ops detail, not a Rails thing. It matters because Rails-shop culture expects release branches. Most of the Rails teams I've worked in or read about branch from &lt;code&gt;main&lt;/code&gt; for releases and maintain those branches for the release's lifetime. When you build tooling that assumes this, you're building on ground that already holds the weight.&lt;/p&gt;

&lt;p&gt;Not every ecosystem works this way. I've watched teams attempt the same pattern against a polyglot stack where some services cut tags, others branched, others shipped trunk-based with feature flags. Every service had a different answer to "what code is the customer running right now." Each variation was a bug waiting to happen. Rails shops tend to vary less on this axis, and that homogeneity is worth real money when you're trying to pin an agent to the truth.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. &lt;code&gt;config/routes.rb&lt;/code&gt; is the densest map of application structure I've ever handed to an LLM
&lt;/h2&gt;

&lt;p&gt;When a ticket says &lt;em&gt;"the customer is seeing a 500 on the study-closeout page,"&lt;/em&gt; the agent does this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads &lt;code&gt;config/routes.rb&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Identifies the controller action.&lt;/li&gt;
&lt;li&gt;Opens the controller.&lt;/li&gt;
&lt;li&gt;Follows to the service.&lt;/li&gt;
&lt;li&gt;Lands on the specific method where the exception is raised.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I've watched this exact chain complete in under a minute on a real ticket, zero guidance from a human. No custom index, no pre-built call graph, no LSP server in the loop. &lt;strong&gt;One routes file, read top to bottom.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That file tells the truth. It's one place. It reads like a table of contents. No custom dispatcher, no decorators rewriting paths at runtime, no middleware hijacking the first matching route. Just &lt;code&gt;resources :studies&lt;/code&gt;, with nested routes for the actions that matter.&lt;/p&gt;

&lt;p&gt;This is the single highest-leverage file in a Rails app for an agent to read. If you've been putting off cleaning up your routes file, now you have a new reason.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I was wrong about
&lt;/h2&gt;

&lt;p&gt;I expected to spend the first month of this project writing &lt;em&gt;helpers&lt;/em&gt; for the agent — summaries, structured manifests, entity graphs, canonicalized module docs. The classic "AI-friendly codebase requires extra scaffolding" thesis you see a lot of right now.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I wrote none of it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The agent reads &lt;code&gt;config/routes.rb&lt;/code&gt;, navigates &lt;code&gt;app/services/&lt;/code&gt;, reads Active Record scopes and follows associations, reads named jobs and understands them. The scaffolding I'd pre-written to help it turned out to be noise the model had to weigh against the code itself. The most useful thing I did was &lt;strong&gt;delete&lt;/strong&gt; the "helpful" entity summaries I'd generated up front and trust the agent to read the code instead.&lt;/p&gt;

&lt;p&gt;If you take one concrete thing from this post, let it be that. &lt;strong&gt;Don't pre-chew the codebase for the model. Let it read what your new-hire Rails engineer reads.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The broader point
&lt;/h2&gt;

&lt;p&gt;There's a common framing that AI-native development will require "AI-friendly" codebases written in some new way. My experience points the other direction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI-friendly codebase is the one that was already legible to the tenth engineer who joined the team on day one.&lt;/strong&gt; Rails has been optimizing for that engineer for twenty years. Shared conventions. Declarative syntax. Predictable layout. Boring, obvious names.&lt;/p&gt;

&lt;p&gt;Rails didn't set out to be AI-friendly. It set out to make humans productive by reducing the number of arbitrary decisions they had to make. LLMs turn out to want the exact same thing, for the exact same reason: &lt;strong&gt;both are pattern-matching engines whose cost of understanding drops when the code looks like every other codebase of its kind.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rails optimized for the human version. It gets the LLM version for free.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where this is going
&lt;/h2&gt;

&lt;p&gt;What I've described here is the substrate — the reason any of this was tractable to begin with. The actual system on top of it — what the agent does with this legibility, the invariants it defends, the specific places it still fails, what we had to build around it — is a longer story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'm submitting the full writeup as a talk at Rails World 2026.&lt;/strong&gt; There's no public version of the system yet, and there won't be before the talk. If it gets accepted, come find me in Austin. If it doesn't, I'll write it up afterwards.&lt;/p&gt;

&lt;p&gt;In the meantime, if you're experimenting with agents against your own Rails app, I'd genuinely love to hear what you've found — especially the things that broke in ways you didn't expect. That's where the interesting material lives, and it's what doesn't make it into blog posts until someone shares it first.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Raj Kumar is a Toronto-based tech lead. He writes Rails for a living and is currently most interested in the seam between Rails applications and LLM agents.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>ai</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
