<?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: C Varun</title>
    <description>The latest articles on DEV Community by C Varun (@c-varun14).</description>
    <link>https://dev.to/c-varun14</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%2F4192668%2F345ce9c8-84e1-4d5b-826e-8d4b790cdc82.png</url>
      <title>DEV Community: C Varun</title>
      <link>https://dev.to/c-varun14</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/c-varun14"/>
    <language>en</language>
    <item>
      <title>I was losing interest in building, so I built an AI agency on Switch</title>
      <dc:creator>C Varun</dc:creator>
      <pubDate>Sat, 07 Nov 2026 18:19:03 +0000</pubDate>
      <link>https://dev.to/c-varun14/i-was-losing-interest-in-building-so-i-built-an-ai-agency-on-switch-1hgh</link>
      <guid>https://dev.to/c-varun14/i-was-losing-interest-in-building-so-i-built-an-ai-agency-on-switch-1hgh</guid>
      <description>&lt;p&gt;I was spending so long on the boring parts of building software that I started losing interest in building itself.&lt;/p&gt;

&lt;p&gt;So I built Backroom: a one-person software agency that runs from a Discord chat, on Switch. In a real run, two client documents that contradicted each other became an agreed brief in 30 minutes. That used to take me three to four days.&lt;/p&gt;

&lt;p&gt;This post is how it works, what broke the first time it went live, and how you can set up the same shape yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem was never the code
&lt;/h2&gt;

&lt;p&gt;I run WebbyWasp, a small software agency, on my own. I build apps for startups testing new ideas. Most of them start with under 1,000 users, and they have no reason to pay big cloud bills for something that might not work.&lt;/p&gt;

&lt;p&gt;So I keep costs tiny. For an earlier hackathon I built &lt;a href="https://github.com/c-varun14/pgfy" rel="noopener noreferrer"&gt;PgFy&lt;/a&gt;, because paying for a separate database per project made no sense: 50 databases for $12 a month. My client apps' databases run on it today.&lt;/p&gt;

&lt;p&gt;Money wasn't the real cost, though. Time was.&lt;/p&gt;

&lt;p&gt;Every new project meant the same work. Set up git. Copy compose IDs. Manage environment variables in Dokploy. Click through deploys. Then sync WhatsApp chats, task boards and every other project, and carry all of it to an AI agent by hand. Agreeing one final document took three to four days, partly because I kept putting off the work around it.&lt;/p&gt;

&lt;p&gt;None of that was building. It was just tedious.&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%2Fkkuoh25ig6k9bwl9k5zu.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%2Fkkuoh25ig6k9bwl9k5zu.png" alt="How one job moves through Backroom: client in Discord, agents in a private work room, owner approvals, GitHub, keys and NixOS" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Backroom does
&lt;/h2&gt;

&lt;p&gt;A client talks in Discord. A team of agents does the work behind the scenes, and I only make the decisions that commit the business.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lead (Claude)&lt;/strong&gt; reads the client's documents, finds contradictions, and writes the brief and technical plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Builder (Codex)&lt;/strong&gt; builds on a branch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reviewer (a fresh Codex)&lt;/strong&gt; tests the running app without ever seeing the builder's conversation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desk (plain code, no AI)&lt;/strong&gt; holds the approvals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule behind all of it: &lt;strong&gt;the agents decide the work, and plain code holds the keys.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One real run
&lt;/h2&gt;

&lt;p&gt;I tested this on real accounts with a demo project called Cedar Studio.&lt;/p&gt;

&lt;p&gt;The client pressed &lt;strong&gt;Start a project&lt;/strong&gt;, and the project was ready in 26 seconds. Then they posted two documents. One required a phone number on a quote form. The other said email only.&lt;/p&gt;

&lt;p&gt;The lead caught that conflict and asked about it in the chat. It also found a second one nobody planted: the client wanted Polar plans, but the other document said no other integrations.&lt;/p&gt;

&lt;p&gt;The client answered in one line: "Email only, please. Don't ask for a phone number. And yes, include the Polar plans section."&lt;/p&gt;

&lt;p&gt;The lead worked out the rest, and the client confirmed the final brief 30 minutes after posting the documents. That time includes bugs I was fixing live.&lt;/p&gt;

&lt;p&gt;Then GitHub filled itself in. A milestone, an issue with 12 acceptance criteria, a branch, commits signed by a Backroom Builder identity, and a pull request with CI. I never opened GitHub to do any of it.&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%2F0lwr6cttq8l0hvjg98w1.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%2F0lwr6cttq8l0hvjg98w1.png" alt="GitHub issue created automatically with 12 acceptance criteria" width="799" height="578"&gt;&lt;/a&gt;&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%2Fnixh64x9w1uib43jdnpm.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%2Fnixh64x9w1uib43jdnpm.png" alt="Commits on the branch, signed by the Backroom Builder identity" width="799" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The builder produced what the client agreed: a quote form with no phone field, and a Polar plans page in the admin dashboard.&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%2Fx9ci2sz2j7wp1yizqtfk.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%2Fx9ci2sz2j7wp1yizqtfk.png" alt="Quote form with name, email and description, and no phone field" width="800" height="500"&gt;&lt;/a&gt;&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%2F6cvc3dgtym79uqib7av2.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%2F6cvc3dgtym79uqib7av2.png" alt="Polar plans admin page" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Switch is the backbone
&lt;/h2&gt;

&lt;p&gt;Backroom doesn't just use Switch. It stops without it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rooms are the wall.&lt;/strong&gt; Clients and agents live in separate Switch rooms. When an agent tries to reach a client room, Switch refuses with HTTP 403. I didn't have to build that isolation myself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verified identity is the release gate.&lt;/strong&gt; Every approval is tied to a verified sender. An agent's message can never count as mine, so an agent can't approve its own work. A prompt injection has nothing to talk to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Handoffs run through it.&lt;/strong&gt; Claude passes work to Codex through Switch, so two different vendors work as one team.&lt;/p&gt;

&lt;p&gt;You can test this yourself with no accounts. &lt;code&gt;bash demo.sh&lt;/code&gt; runs 49 steps on a real Switch stack, using scripted roles. Then it turns Switch off, and five real operations fail.&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%2F75g8t0apvravvjzbfsy9.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%2F75g8t0apvravvjzbfsy9.png" alt="With Switch off, five real operations fail" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What replaced the clicking
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;NixOS replaced Dokploy.&lt;/strong&gt; The deployer builds one exact commit and runs it in its own sandbox on a server I control. A deploy is one tap, not a build cycle I babysit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The harness layer makes agents swappable.&lt;/strong&gt; Each role is one line of config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;lead&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;harness&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;claude&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;sonnet&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;harness&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;codex&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;gpt-6-luna&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;reviewer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;harness&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;codex&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;gpt-6.1-sol&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trying a better model is a one line change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keys are the one chore left.&lt;/strong&gt; I couldn't remove environment variables, but I could shrink them. The builder declares a name in &lt;code&gt;.env.required&lt;/code&gt;, and I paste the value once into a private page. Saved values are never shown again, and they never reach an agent or the chat.&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%2Fary7xt9s5j4jir2072v5.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%2Fary7xt9s5j4jir2072v5.png" alt="Before and after: every step of the workflow" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What broke when it went live
&lt;/h2&gt;

&lt;p&gt;Every test passed before this run. Then the real path found what tests couldn't, because tests use stand-ins and these bugs lived in the joins between real systems.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The desk couldn't verify senders.&lt;/strong&gt; It deliberately has no Docker access, so its identity lookup failed on every message, silently. A tiny read-only helper fixed it, without giving the desk root-level access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agents were locked out of their own folders.&lt;/strong&gt; The login error blamed an old install. The real cause was folder permissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approval cards lost their buttons.&lt;/strong&gt; Switch drops them from cards that are too long.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A one-second loop burned GitHub's whole hourly limit.&lt;/strong&gt; Polling now runs once a minute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The sandbox protected &lt;code&gt;.git&lt;/code&gt;,&lt;/strong&gt; so the builder couldn't commit until it got that one path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-memory tests hid a real Postgres bug&lt;/strong&gt; that only CI could see.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My favourite: the builder refused to start because a document still said "proposed", even though I had approved it. That refusal was wrong, and it was exactly the caution I want.&lt;/p&gt;

&lt;p&gt;The lesson: a green test suite doesn't prove the pieces fit. Send one real message through every hop before you trust anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set this up yourself
&lt;/h2&gt;

&lt;p&gt;If you are new to Switch, this is the shape that worked:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run Switch and connect your agents through its agent protocol. I used Claude Code and Codex.&lt;/li&gt;
&lt;li&gt;Bridge a Discord forum to a client room, and keep agents out of that room. Give them a private work room.&lt;/li&gt;
&lt;li&gt;Put one plain-code agent between the two rooms. Make it the only thing allowed to cross the wall.&lt;/li&gt;
&lt;li&gt;Use Switch's verified sender identity for every approval.&lt;/li&gt;
&lt;li&gt;Turn Switch off and confirm your workflow really stops.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href="https://github.com/c-varun14/Backroom" rel="noopener noreferrer"&gt;README&lt;/a&gt; has the setup, and &lt;code&gt;bash demo.sh&lt;/code&gt; proves the wall in about a minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stands
&lt;/h2&gt;

&lt;p&gt;The workflow above ran on real accounts, up to a built app with an open pull request. Independent review and deploy for this project are the next stretch, and the code path that lets small safe changes ship with no taps from me is tested but hasn't run live yet. The reviewer catches I rely on so far came from rehearsal, including an admin page showing "Section not found".&lt;/p&gt;

&lt;p&gt;I'm rolling Backroom out to production for a real client next.&lt;/p&gt;

&lt;p&gt;The biggest change isn't the 30 minutes. It's that I'm building again, instead of configuring.&lt;/p&gt;

&lt;p&gt;One person. A whole team.&lt;/p&gt;

&lt;p&gt;Code: &lt;a href="https://github.com/c-varun14/Backroom" rel="noopener noreferrer"&gt;github.com/c-varun14/Backroom&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>hackathon</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Why does the spec say that? Building a company brain on FalkorDB that proves every answer</title>
      <dc:creator>C Varun</dc:creator>
      <pubDate>Sun, 18 Oct 2026 18:25:28 +0000</pubDate>
      <link>https://dev.to/c-varun14/why-does-the-spec-say-that-building-a-company-brain-on-falkordb-that-proves-every-answer-16jo</link>
      <guid>https://dev.to/c-varun14/why-does-the-spec-say-that-building-a-company-brain-on-falkordb-that-proves-every-answer-16jo</guid>
      <description>&lt;p&gt;&lt;em&gt;Graph Hacks 2026 · Track 03: Company Brain · &lt;a href="http://ratified.webbywasp.com" rel="noopener noreferrer"&gt;Live demo&lt;/a&gt; · &lt;a href="https://github.com/c-varun14/ratified" rel="noopener noreferrer"&gt;Code&lt;/a&gt; · &lt;a href="https://youtu.be/WX62wgV81MA" rel="noopener noreferrer"&gt;Demo video&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On 1 March 2018, someone on the IETF TLS mailing list wrote a message about draft-ietf-tls-tls13-24 and mentioned Section 4.2.1. Five people, six messages and three days later, the thread went quiet. Eventually the draft became RFC 8446, and Section 4.2.1 became the place where TLS 1.3 announces its version through a &lt;code&gt;supported_versions&lt;/code&gt; extension instead of the version field every earlier TLS used.&lt;/p&gt;

&lt;p&gt;If you join a team that ships TLS 1.3 today, you will wonder why it works that way. The reason is in that thread. Finding it takes a person with a good memory or an afternoon. Most companies have this problem in private, in a chat history nobody can search by &lt;em&gt;meaning&lt;/em&gt;. We wanted to see whether a graph could do it, and we picked the one place where we could prove it: a mailing list that is completely public.&lt;/p&gt;

&lt;p&gt;This is the story of how Ratified follows that one email to the RFC, and the mistakes we made on the way.&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%2F41of3tocb1hud9n4nlnj.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%2F41of3tocb1hud9n4nlnj.png" alt="The answer card for RFC 8446 §4.2.1" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule one: no link, no answer
&lt;/h2&gt;

&lt;p&gt;Before any code, we set one rule. A company brain that can't be checked is just a confident guesser, so every answer has to end in a link a stranger can open. That rule decided most of the design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Answers come from graph traversals, not from a model's memory.&lt;/li&gt;
&lt;li&gt;No model writes the Cypher. A deterministic router picks &lt;strong&gt;one hand-written query&lt;/strong&gt; per question.&lt;/li&gt;
&lt;li&gt;If the traversal returns nothing, the card says &lt;strong&gt;"the graph does not know"&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: the clue is a message
&lt;/h2&gt;

&lt;p&gt;Everything starts with a &lt;code&gt;Message&lt;/code&gt;. We pulled 3,078 messages from the TLS list, 1,324 of them with their full text, plus 633 threads and 403 people. Each message links to its author and its thread.&lt;/p&gt;

&lt;p&gt;The first mistake was assuming "Section 4.2.9" in a message means Section 4.2.9 of the RFC. It doesn't. Section numbers moved between drafts. A message about draft -21 that says "4.2.9" means &lt;em&gt;-21's&lt;/em&gt; 4.2.9, which became RFC §4.2.10.&lt;/p&gt;

&lt;p&gt;So a message can't point at the RFC directly. It points at a &lt;strong&gt;draft revision&lt;/strong&gt;, and the revision points at the section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: the trail runs through draft revisions
&lt;/h2&gt;

&lt;p&gt;We loaded 113 drafts and their 839 revisions from the datatracker, chained with &lt;code&gt;SUPERSEDED&lt;/code&gt;, and the 66 RFCs they became. Then came the part that took the longest: &lt;code&gt;ALIGNS_SECTION&lt;/code&gt;, which maps each section of each revision to the RFC section it turned into. It matches by &lt;strong&gt;heading text&lt;/strong&gt;, because numbers lie and headings mostly don't. It also records whether the text was still changing, meaning less than 98% similar to what shipped.&lt;/p&gt;

&lt;p&gt;Now a citation can be resolved: message → named revision → aligned RFC section. A citation that can't be resolved is dropped rather than guessed.&lt;/p&gt;

&lt;p&gt;The whole trail is one query, and the last hop is a variable-length walk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dr&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:SUPERSEDED&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mf"&gt;0.&lt;/span&gt;&lt;span class="n"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;last:&lt;/span&gt;&lt;span class="n"&gt;DraftRevision&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:BECAME&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;rfc:&lt;/span&gt;&lt;span class="n"&gt;RFC&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:HAS_SECTION&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;IN&lt;/span&gt; &lt;span class="nf"&gt;nodes&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;n.id&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rfc.id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rfc&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s.number&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For §4.2.1 it returns the chain message → thread → draft -24 → -25 → -26 → -27 → -28 → RFC 8446 → §4.2.1. Every node on that path is a link to the mail archive, the datatracker or the RFC Editor.&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%2Fv43r2e6zqqt8ywjc9mnn.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%2Fv43r2e6zqqt8ywjc9mnn.png" alt="The path FalkorDB walked, from the March 2018 message to RFC 8446 §4.2.1" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: only count people who were there while it was moving
&lt;/h2&gt;

&lt;p&gt;Our first version had a flaw we only saw by reading the output. A name called "RFC Errata System" showed up as someone who "discussed" §4.2.10 before it shipped. Errata reports name draft -28, but years after the fact.&lt;/p&gt;

&lt;p&gt;The fix was a rule in the query: a message counts only if it was written &lt;strong&gt;while the revision it names was current&lt;/strong&gt;, before the next revision or before the RFC's publication month. After that, §4.2.1 had a clean answer: 6 messages, 5 people, March 2018, written while the clause still differed from what shipped.&lt;/p&gt;

&lt;p&gt;We also stopped saying people "argued against" it. The edges show a message cited a clause while it was changing, and some of those people were the draft's own editors. The card now says &lt;em&gt;discussed while still changing&lt;/em&gt;, and, further down, &lt;strong&gt;connected, not caused&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: now find the humans
&lt;/h2&gt;

&lt;p&gt;Once you have the thread, the next question is who to ask. The card lists the five people from the thread, plus the current chairs and area directors, and drafts a question with the sources attached. It never sends it.&lt;/p&gt;

&lt;p&gt;There's also a "who knows what" ranking. We built a &lt;code&gt;CO_DISCUSSED&lt;/code&gt; projection (two people who wrote in the same thread) with Cypher at ingest, then ran &lt;code&gt;algo.pageRank&lt;/code&gt; and &lt;code&gt;algo.betweenness&lt;/code&gt; over it. The draft's editor ranks first on both, with one of the chairs second.&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%2F21xckmultk88h0o6j23j.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%2F21xckmultk88h0o6j23j.png" alt="PageRank and betweenness over who talked with whom" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The trap here: PageRank follows edge direction, and a pair of people has no direction. With one edge per pair, the ranking came out in alphabetical order. Write the projection edges &lt;strong&gt;in both directions&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nobody types "section 4.2.10"
&lt;/h2&gt;

&lt;p&gt;People ask "why does early data work like this?" A full-text index on section headings turns that into §4.2.10 Early Data Indication, shows which words matched, and offers the runner-ups as one-click alternatives.&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%2Ficevbtveuqesfb9padno.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%2Ficevbtveuqesfb9padno.png" alt="Plain words resolve to §4.2.10, with the matched words and runner-ups" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two things we learned only by testing against the server. Space-separated terms are ANDed, so a casual question with one extra word matched nothing until we OR-ed them. And indexing the section &lt;em&gt;text&lt;/em&gt; made results worse, because broad overview sections outranked exact headings. We index headings only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every answer shows its homework
&lt;/h2&gt;

&lt;p&gt;Each card has a panel listing the Cypher FalkorDB ran, its parameters, row counts and the database's own &lt;code&gt;run_time_ms&lt;/code&gt;. For the §4.2.1 question that's 4 queries, under a millisecond in total on a laptop. We'd copy this into any graph product. It turns "trust me" into "here is the query".&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%2Fmrcfyrrz7we65svlmn51.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%2Fmrcfyrrz7we65svlmn51.png" alt="The Cypher behind an answer card" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Asking what the graph doesn't know
&lt;/h2&gt;

&lt;p&gt;Ask about IESG ballots or meeting minutes, which we never ingested, and you get a grey card that says the graph doesn't know. That is the feature we trust most.&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%2Fpr5iotwd03pqfn4po20w.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%2Fpr5iotwd03pqfn4po20w.png" alt="A grey card: the graph does not know" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Did it beat plain search?
&lt;/h2&gt;

&lt;p&gt;We wrote 13 questions and derived the answer keys with a separate parser from the primary record. The graph answers 13/13. A BM25 plus bge-small retriever over the same text gets 1/10 exact and 3/10 in its top 10, with three count questions it can't score. That headline flatters us, so here is the fair version: on the three questions whose evidence sits in text a retriever can see, it's &lt;strong&gt;graph 3/3, retrieval 1/3&lt;/strong&gt;. On revision lists and counts the answer isn't in any text, it's structure, which is the case for a graph but not really a retrieval failure.&lt;/p&gt;

&lt;p&gt;The router was tuned on those 13, so we had a second model write 40 new questions without seeing it. First pass, untouched: &lt;strong&gt;30/40&lt;/strong&gt;, with 5 confidently wrong routes. The scorer first said 34. Reading the rows showed four questions that took "TLS 1.3" as section 1.3. We fixed it, so that set is no longer held out.&lt;/p&gt;

&lt;p&gt;Other things that went wrong and got caught: every revision link we rendered was a 404 until a click-check script found it, one person appeared twice under two email addresses, and a first bench run credited retrieval with a count because the digits appeared in a chunk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stops
&lt;/h2&gt;

&lt;p&gt;The graph can show a thread and a changed clause are connected. It can't show one caused the other. Meetings, GitHub issues and IESG ballots are outside the record. 47 of RFC 8446's 84 sections have a traced discussion, and the rest are answered as partial. We proved the idea on one working group, not on a company's private chat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="http://ratified.webbywasp.com" rel="noopener noreferrer"&gt;http://ratified.webbywasp.com&lt;/a&gt;. Click &lt;em&gt;Try an example&lt;/em&gt;, then open &lt;em&gt;Show how this was computed&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/c-varun14/ratified" rel="noopener noreferrer"&gt;https://github.com/c-varun14/ratified&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run it yourself:&lt;/strong&gt; &lt;code&gt;docker compose up --build&lt;/code&gt;, then &lt;a href="http://127.0.0.1:8000" rel="noopener noreferrer"&gt;http://127.0.0.1:8000&lt;/a&gt;. It rebuilds the graph offline from the committed record, with no keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We used AI coding assistants while building this and documented how in &lt;code&gt;AI_USE.md&lt;/code&gt;.&lt;/p&gt;

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