<?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: Rishik</title>
    <description>The latest articles on DEV Community by Rishik (@rishik92).</description>
    <link>https://dev.to/rishik92</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%2F4192412%2F7715af0a-f475-4486-9335-97207221dbd5.jpg</url>
      <title>DEV Community: Rishik</title>
      <link>https://dev.to/rishik92</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rishik92"/>
    <language>en</language>
    <item>
      <title>Every Fact Cited, Dated and Disputed: Building Nodus on FalkorDB</title>
      <dc:creator>Rishik</dc:creator>
      <pubDate>Sun, 18 Oct 2026 16:37:56 +0000</pubDate>
      <link>https://dev.to/rishik92/every-fact-cited-dated-and-disputed-building-nodus-on-falkordb-14</link>
      <guid>https://dev.to/rishik92/every-fact-cited-dated-and-disputed-building-nodus-on-falkordb-14</guid>
      <description>&lt;h2&gt;
  
  
  The question nobody can answer
&lt;/h2&gt;

&lt;p&gt;"Who owns &lt;code&gt;ledger-service&lt;/code&gt;?" sounds like a ten-second question. In most companies it takes an afternoon.&lt;/p&gt;

&lt;p&gt;The service catalog says Platform. A design doc from last spring says Payments. A Slack thread from Tuesday says &lt;em&gt;"Infra owns Ledger now."&lt;/em&gt; The person who would know left in the reorg. Each source is a little bit right, and none of them says &lt;strong&gt;when&lt;/strong&gt; it was right.&lt;/p&gt;

&lt;p&gt;The usual AI answer is to throw all of it into a vector store and let a model summarize. That gives you a fluent paragraph, but it can't tell you which source won, whether the answer was true last quarter or is true today, or what it is quietly making up. For questions about ownership, accountability and decisions, &lt;strong&gt;a confident wrong answer is worse than no answer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wanted a company brain that shows its receipts. That became &lt;strong&gt;Nodus&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Nodus is
&lt;/h2&gt;

&lt;p&gt;Nodus is an organizational knowledge graph built entirely on &lt;a href="https://www.falkordb.com/" rel="noopener noreferrer"&gt;FalkorDB&lt;/a&gt;. It pulls people, teams, systems, projects, tickets, docs, chats and architecture decisions (ADRs) into one bi-temporal graph. It answers &lt;em&gt;"who owns this?"&lt;/em&gt;, &lt;em&gt;"who knows about this?"&lt;/em&gt; and &lt;em&gt;"why was this decided?"&lt;/em&gt;, and every answer comes with an exact quote from its source.&lt;/p&gt;

&lt;p&gt;The core idea is simple: &lt;strong&gt;a fact is not a chunk of text; it is a versioned claim with evidence.&lt;/strong&gt; Every claim Nodus learns becomes an &lt;code&gt;Assertion&lt;/code&gt; node that points at the exact character range of the &lt;code&gt;Span&lt;/code&gt; it came from. Each assertion knows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;When it was true in the real world:&lt;/strong&gt; valid time (&lt;code&gt;valid_from&lt;/code&gt;, &lt;code&gt;valid_to&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When Nodus learned or retracted it:&lt;/strong&gt; transaction time (&lt;code&gt;recorded_at&lt;/code&gt;, &lt;code&gt;retracted_at&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How much to trust it:&lt;/strong&gt; authority (&lt;code&gt;4&lt;/code&gt; structured data or human confirmation, &lt;code&gt;2&lt;/code&gt; docs, &lt;code&gt;1&lt;/code&gt; chat, &lt;code&gt;0&lt;/code&gt; drafts)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two clocks let Nodus answer two different historical questions. &lt;em&gt;"Who owned Ledger before the reorg?"&lt;/em&gt; asks about reality. &lt;em&gt;"What did we **believe&lt;/em&gt;* about Ledger last month?"* asks about the brain's own state. Most systems can't tell those apart. Nodus can, and nothing is ever deleted.&lt;/p&gt;

&lt;p&gt;FalkorDB is the only datastore. Storage, vector and full-text search, graph algorithms, temporal reasoning and permission checks all happen inside it. The rest of this post covers the four hardest design problems and how FalkorDB solved each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 1: Full history without slow queries
&lt;/h2&gt;

&lt;p&gt;The first design fought itself. If every fact is its own node, you keep perfect provenance, but a question like &lt;em&gt;"what breaks if Ledger goes down?"&lt;/em&gt; has to walk through an assertion node at every hop. If facts are plain edges, traversal is fast, but the moment ownership changes you lose the history and the conflicting claims.&lt;/p&gt;

&lt;p&gt;I stopped choosing and kept both layers in the same graph:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;An immutable assertion ledger.&lt;/strong&gt; &lt;code&gt;Source -[:HAS_SPAN]-&amp;gt; Span &amp;lt;-[:EVIDENCED_BY]- Assertion&lt;/code&gt;, with assertions linked to each other by &lt;code&gt;SUPERSEDES&lt;/code&gt;, &lt;code&gt;CONTINUES&lt;/code&gt;, &lt;code&gt;DISPUTES&lt;/code&gt; and &lt;code&gt;CONFIRMS&lt;/code&gt;. This layer is append-only and holds the full story.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A materialized edge cache.&lt;/strong&gt; Every &lt;em&gt;current&lt;/em&gt; assertion is also projected onto a direct edge (&lt;code&gt;OWNS&lt;/code&gt;, &lt;code&gt;LEADS&lt;/code&gt;, &lt;code&gt;DEPENDS_ON&lt;/code&gt;, &lt;code&gt;PART_OF&lt;/code&gt;, &lt;code&gt;AFFECTS&lt;/code&gt;, &lt;code&gt;REPLACES&lt;/code&gt;). Each edge carries &lt;code&gt;{aid, valid_from, valid_to, authority}&lt;/code&gt;, where &lt;code&gt;aid&lt;/code&gt; points back to its assertion.&lt;/li&gt;
&lt;/ol&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%2Fzl03pka4lh59zcfxgxcc.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%2Fzl03pka4lh59zcfxgxcc.png" alt="Dual-layer design: assertion ledger and materialized edges, joined by aid" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Queries walk the fast edges on the right; the dashed &lt;code&gt;aid&lt;/code&gt; link is how any answer finds its way back to the claim and quote on the left.&lt;/p&gt;

&lt;p&gt;Operational queries run on the fast layer and only touch the ledger to fetch citations. Here is the blast-radius query:&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="py"&gt;x:&lt;/span&gt;&lt;span class="n"&gt;Entity&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt;&lt;span class="n"&gt;$x&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:DEPENDS_ON&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="m"&gt;3&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="py"&gt;s:&lt;/span&gt;&lt;span class="n"&gt;Entity&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;IN&lt;/span&gt; &lt;span class="nf"&gt;relationships&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="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;e.valid_from&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;$V&lt;/span&gt; &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;e.valid_to&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;$V&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;s.id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s.name&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;length&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="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;hops&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;IN&lt;/span&gt; &lt;span class="nf"&gt;relationships&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;e.aid&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;aids&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;hops&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is a &lt;a href="https://docs.falkordb.com/cypher/match" rel="noopener noreferrer"&gt;variable-length traversal&lt;/a&gt; with a time filter on every hop, and on the demo graph it returns in under a millisecond on FalkorDB's &lt;a href="https://docs.falkordb.com/design" rel="noopener noreferrer"&gt;sparse adjacency matrices&lt;/a&gt;. The &lt;code&gt;aids&lt;/code&gt; it returns resolve straight to the assertions and quoted spans behind each dependency, so &lt;strong&gt;the answer cites its sources at no extra cost.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Problem 2: When sources disagree
&lt;/h2&gt;

&lt;p&gt;Real companies contradict themselves constantly, so contradiction handling had to be a first-class rule rather than an edge case. Nodus uses one rule: &lt;strong&gt;a new claim only replaces an old one if its authority is equal or higher.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Equal or higher authority:&lt;/strong&gt; the new assertion &lt;code&gt;SUPERSEDES&lt;/code&gt; the old one. The old one gets a &lt;code&gt;valid_to&lt;/code&gt;, its edge drops out of the current cache, and it stays in the ledger forever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower authority:&lt;/strong&gt; the new assertion is recorded as &lt;code&gt;DISPUTES&lt;/code&gt;. Current truth doesn't change, but the conflict is visible and can be routed to someone who can settle it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The demo shows this end to end. The catalog (authority &lt;code&gt;4&lt;/code&gt;) says Payments owns Ledger. Someone posts &lt;em&gt;"Infra owns Ledger now"&lt;/em&gt; in Slack (authority &lt;code&gt;1&lt;/code&gt;). Nodus doesn't flip ownership based on a chat message. It records a dispute, keeps answering "Payments", and flags the conflict. Once the Payments lead confirms the handover, that confirmation (authority &lt;code&gt;4&lt;/code&gt;) supersedes the old fact and Infra becomes the owner, with the full chain of evidence intact.&lt;/p&gt;

&lt;p&gt;The same ledger powers &lt;strong&gt;stale-doc detection&lt;/strong&gt;. One query finds the latest version of each wiki page that still asserts a fact superseded after the page was last edited. Outdated docs flag themselves.&lt;/p&gt;

&lt;p&gt;A rule like this is easy to get subtly wrong, so I tested it with &lt;strong&gt;property-based tests&lt;/strong&gt;. They feed the same messages in random orders and check that the final state always converges to the same answer. If arrival order changed the outcome, the brain would be guessing.&lt;/p&gt;
&lt;h2&gt;
  
  
  Problem 3: Graph algorithms at any point in time
&lt;/h2&gt;

&lt;p&gt;Some questions aren't about one node's neighbors. They're about the shape of the whole organization: which systems are most critical, who is a single point of failure, where the silos are. FalkorDB ships &lt;a href="https://docs.falkordb.com/algorithms" rel="noopener noreferrer"&gt;native GraphBLAS implementations&lt;/a&gt; of exactly the algorithms for these, and they are fast.&lt;/p&gt;

&lt;p&gt;The catch is that they filter by &lt;strong&gt;node label and relationship type&lt;/strong&gt;, not by property predicates evaluated per hop. I couldn't pass &lt;code&gt;valid_from &amp;lt;= $V &amp;lt; valid_to&lt;/code&gt; into PageRank, and running on the live graph would mix in edges from every era.&lt;/p&gt;

&lt;p&gt;So Nodus builds &lt;strong&gt;snapshot graphs&lt;/strong&gt;. For a timestamp &lt;code&gt;$V&lt;/code&gt;, it materializes a lightweight read-only graph named &lt;code&gt;&amp;lt;graph&amp;gt;__snap_&amp;lt;YYYYMMDD&amp;gt;&lt;/code&gt; that contains only the edges valid at that moment, plus pre-aggregated helper edges. The native algorithms run on the snapshot:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;FalkorDB procedure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Which systems are most critical?&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.falkordb.com/algorithms/pagerank" rel="noopener noreferrer"&gt;&lt;code&gt;algo.pageRank('System', 'DEPENDS_ON')&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who are our single points of failure?&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.falkordb.com/algorithms/betweenness-centrality" rel="noopener noreferrer"&gt;&lt;code&gt;algo.betweenness&lt;/code&gt;&lt;/a&gt; over undirected &lt;code&gt;LINK&lt;/code&gt; edges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where are our knowledge gaps and silos?&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.falkordb.com/algorithms/wcc" rel="noopener noreferrer"&gt;&lt;code&gt;algo.WCC&lt;/code&gt;&lt;/a&gt; over &lt;code&gt;OWNS&lt;/code&gt;, &lt;code&gt;LEADS&lt;/code&gt;, &lt;code&gt;MEMBER_OF&lt;/code&gt;, &lt;code&gt;PART_OF&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How is Bob connected to Ledger?&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.falkordb.com/algorithms/sppath" rel="noopener noreferrer"&gt;&lt;code&gt;algo.SPpaths&lt;/code&gt;&lt;/a&gt;, top 3 paths, up to 4 hops, both directions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Algorithm output is treated as a &lt;strong&gt;ranking, not a claim&lt;/strong&gt;. Before Nodus names anyone as a single point of failure, it joins the result back to the primary graph and attaches the assertions and quotes that put them in that position. Because snapshots are per-date, you can ask what the risk picture looked like before the reorg and compare it with today.&lt;/p&gt;
&lt;h2&gt;
  
  
  Problem 4: Writes that can't race
&lt;/h2&gt;

&lt;p&gt;Nodus doesn't only answer questions; it acts. It routes tickets, settles disputes, drafts superseding ADRs and undoes actions. Each of those needs a permission check: only the lead of the team that currently owns a system can approve handing it to another team.&lt;/p&gt;

&lt;p&gt;The usual pattern is to check permissions in application code, then write. That leaves a &lt;strong&gt;time-of-check to time-of-use (TOCTOU)&lt;/strong&gt; gap. Between the check and the write, ownership might change, or someone else might settle the same dispute.&lt;/p&gt;

&lt;p&gt;The fix was to &lt;strong&gt;move the permission check into the write&lt;/strong&gt;. Every mutation is a single Cypher query. It first checks eligibility against the org graph itself (&lt;code&gt;Person -[:LEADS]-&amp;gt; Team -[:OWNS]-&amp;gt; System&lt;/code&gt;) and confirms the target assertion is still in the state the caller saw. That leaves one question: &lt;em&gt;how do you make a Cypher query abort halfway on purpose?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The answer is a unique constraint and a sentinel. At setup, Nodus creates a &lt;a href="https://docs.falkordb.com/commands/graph.constraint-create" rel="noopener noreferrer"&gt;unique constraint&lt;/a&gt; on &lt;code&gt;:GuardFail(id)&lt;/code&gt; and seeds one node, &lt;code&gt;{id: 'sentinel'}&lt;/code&gt;. Each write query includes a &lt;a href="https://docs.falkordb.com/cypher/foreach" rel="noopener noreferrer"&gt;&lt;code&gt;FOREACH&lt;/code&gt;&lt;/a&gt; guard:&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;FOREACH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="ss"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:GuardFail&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt;&lt;span class="s1"&gt;'sentinel'&lt;/span&gt;&lt;span class="ss"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If the check passed, this does nothing. If it failed, it tries to create a second sentinel, the constraint fires, and FalkorDB rolls back the entire query. &lt;strong&gt;The permission check, the state change, the edge-cache update and the &lt;code&gt;Action&lt;/code&gt; audit node succeed or fail together.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This relies on two guarantees FalkorDB documents in &lt;a href="https://docs.falkordb.com/design/concurrency" rel="noopener noreferrer"&gt;Atomicity and Concurrency Control&lt;/a&gt;: every write query is atomic, so a constraint violation undoes all of it, and writes to a graph are serialized, so the check inside the query always sees the latest committed state.&lt;/p&gt;

&lt;p&gt;Those &lt;code&gt;Action&lt;/code&gt; nodes enable one of my favorite features. Each records what it was &lt;code&gt;BASED_ON&lt;/code&gt; and what it &lt;code&gt;PRODUCED&lt;/code&gt;, so Nodus can show any past action side by side: &lt;strong&gt;what was true when it was taken, and what is true now.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Keeping the LLM honest
&lt;/h2&gt;

&lt;p&gt;In Nodus, the graph is the source of truth and the LLM is optional. When a model is available, it only does three narrow jobs: extracting candidate claims from unstructured text, planning which Cypher templates to run, and phrasing the final answer. &lt;strong&gt;It never decides what is true.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every answer passes a &lt;strong&gt;deterministic verifier&lt;/strong&gt; before it reaches the user. For each claim in the draft, the verifier checks against FalkorDB that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The cited assertion exists.&lt;/li&gt;
&lt;li&gt;The quote falls inside the exact character bounds of its span.&lt;/li&gt;
&lt;li&gt;The tense matches temporal validity, so a superseded fact can't be stated in the present tense.&lt;/li&gt;
&lt;li&gt;Disputed or proposed facts are labeled as such.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the graph lacks evidence for something, Nodus says what is missing instead of filling the gap. &lt;em&gt;"I can't find who owns this after the reorg"&lt;/em&gt; is a useful answer. A plausible guess is not.&lt;/p&gt;

&lt;p&gt;This design has a nice side effect: Nodus runs &lt;strong&gt;fully offline&lt;/strong&gt;. Without an API key, a pattern-based extractor, a rule-based planner and local embeddings take over, and every answer is still cited and verified. Retrieval never leaves FalkorDB either. Embeddings live on &lt;code&gt;Entity&lt;/code&gt; and &lt;code&gt;Span&lt;/code&gt; nodes, so &lt;a href="https://docs.falkordb.com/cypher/indexing/vector-index" rel="noopener noreferrer"&gt;vector search&lt;/a&gt; (&lt;code&gt;db.idx.vector.queryNodes&lt;/code&gt; over &lt;code&gt;vecf32&lt;/code&gt;) and &lt;a href="https://docs.falkordb.com/cypher/indexing/fulltext-index" rel="noopener noreferrer"&gt;full-text search&lt;/a&gt; (&lt;code&gt;db.idx.fulltext.queryNodes&lt;/code&gt;) are fused with graph expansion in the same engine.&lt;/p&gt;

&lt;p&gt;The same read and guarded-write tools are also exposed over &lt;strong&gt;MCP&lt;/strong&gt;, so a coding agent like Claude Code or Cursor can ask &lt;em&gt;"who should review my change to ledger-service?"&lt;/em&gt; from the IDE and get the same cited answer.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Model time before you model anything else
&lt;/h3&gt;

&lt;p&gt;I first treated time as a property I'd add later. That was wrong. Once I separated "when it was true" from "when we learned it", ownership history, stale docs, dispute handling and action audits all fell out of the same two fields. Retrofitting bi-temporality would have meant rewriting every query.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Don't choose between provenance and speed. Cache.
&lt;/h3&gt;

&lt;p&gt;The dual-layer design felt like duplication at first. In practice it's the same trade databases have always made with indexes and materialized views: keep a ledger as the record, and keep a fast projection for reads. The &lt;code&gt;aid&lt;/code&gt; on every edge is the small detail that makes it work.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Learn your engine's primitives, then work with them
&lt;/h3&gt;

&lt;p&gt;I lost time trying to make GraphBLAS algorithms respect per-hop time filters. Once I accepted that they operate on labels and types, snapshot graphs turned out to be cleaner than any workaround. The same goes for the sentinel guard: I didn't need a new transaction feature, just a creative use of a unique constraint.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Put authorization where the data is
&lt;/h3&gt;

&lt;p&gt;Authorization in middleware looked fine until I thought through two people acting at once. Checking permissions inside the same query that writes made a whole class of race conditions &lt;strong&gt;impossible rather than unlikely&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. "I don't know" is a feature
&lt;/h3&gt;

&lt;p&gt;The verifier rejects more drafts than I expected, and every rejection is a hallucination that never reached a user. Being able to say exactly which evidence is missing turned out to be one of the most trusted parts of the demo.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. Order-independence needs proof, not hope
&lt;/h3&gt;

&lt;p&gt;Conflict resolution that works in the order I typed the test cases isn't conflict resolution. Property-based tests that shuffle message arrival caught bugs no hand-written test would have found.&lt;/p&gt;
&lt;h3&gt;
  
  
  7. Make the LLM optional and the system gets better
&lt;/h3&gt;

&lt;p&gt;Forcing the core to work with no model at all made every boundary explicit. The LLM became a convenience layer on top of a system that is correct on its own.&lt;/p&gt;
&lt;h2&gt;
  
  
  Does it actually work? The numbers
&lt;/h2&gt;

&lt;p&gt;All numbers come from the demo company (519 documents, 569 assertions, 140 entities) on FalkorDB 6.0.1. Gold answers come from [the world generator's ground truth, computed without FalkorDB]..&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What I measured&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Test suite&lt;/td&gt;
&lt;td&gt;1,035 passed, 0 failed (also verified on FalkorDB 4.22.0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Order-independence&lt;/td&gt;
&lt;td&gt;200 random assertion chains × 8 time slices, 3 full-corpus shuffled ingestions: identical final state every time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stale facts leaked into answers&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0%&lt;/strong&gt; for Nodus vs 89.1% vector RAG and 100% reranked RAG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disputes surfaced&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Citation recall (358 questions)&lt;/td&gt;
&lt;td&gt;92.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correct entity in answer&lt;/td&gt;
&lt;td&gt;90.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unconfirmed proposals shown as truth&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permission and action scenarios&lt;/td&gt;
&lt;td&gt;13/13 passed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Raw Cypher latency inside FalkorDB (p50, 30 runs): ownership 0.25 ms, 3-hop blast radius 0.24 ms, PageRank 0.29 ms, betweenness 0.98 ms. Full tool calls, including multiple queries and quote lookup, take 3 to 25 ms.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://nodus.rishiklabs.tech/" rel="noopener noreferrer"&gt;https://nodus.rishiklabs.tech/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video walkthrough:&lt;/strong&gt; &lt;a href="https://youtu.be/8PCFA0UFk5s" rel="noopener noreferrer"&gt;https://youtu.be/8PCFA0UFk5s&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/RISHIK92/graph-hackathon" rel="noopener noreferrer"&gt;https://github.com/RISHIK92/graph-hackathon&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/8PCFA0UFk5s" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Ask it &lt;em&gt;"Who owns ledger-service, and what depends on it?"&lt;/em&gt;, &lt;em&gt;"Why did we adopt ADR-007?"&lt;/em&gt; or &lt;em&gt;"Who are our single points of failure?"&lt;/em&gt; Then drag the time slider back before the reorg and ask again. The demo company is fully synthetic, so feel free to break things.&lt;/p&gt;

&lt;p&gt;To run it locally, start FalkorDB with Docker (&lt;code&gt;docker run -p 6379:6379 falkordb/falkordb-server&lt;/code&gt;; see the &lt;a href="https://docs.falkordb.com/operations/docker" rel="noopener noreferrer"&gt;Docker guide&lt;/a&gt;), seed the demo company with &lt;code&gt;brain reset --seed world/seeds/1&lt;/code&gt;, and start the API and UI. Or connect it to Claude Code as an MCP server and ask your IDE who owns the code you're about to change.&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/RISHIK92" rel="noopener noreferrer"&gt;
        RISHIK92
      &lt;/a&gt; / &lt;a href="https://github.com/RISHIK92/graph-hackathon" rel="noopener noreferrer"&gt;
        graph-hackathon
      &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;Nodus: a company brain on FalkorDB&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Track entered: Track 03, Company Brain&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Nodus is an organizational intelligence engine built on FalkorDB. It unifies fragmented company state—people, teams, systems, projects, tickets, documents, chats, and architectural decisions—into a single bi-temporal knowledge graph. Rather than relying on probabilistic RAG retrieval over flat text chunks, Nodus models facts as versioned graph assertions backed by exact source spans. It answers "who owns this?", "who knows about this?", and "why was this decided?" with deterministic provenance, and executes permission-guarded workflows directly inside FalkorDB.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Demo video:&lt;/strong&gt; &lt;a href="https://youtu.be/8PCFA0UFk5s" rel="nofollow noopener noreferrer"&gt;https://youtu.be/8PCFA0UFk5s&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Live deployment:&lt;/strong&gt; &lt;a href="https://nodus.rishiklabs.tech/" rel="nofollow noopener noreferrer"&gt;https://nodus.rishiklabs.tech/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Blog:&lt;/strong&gt; &lt;a href="https://dev.to/rishik92/every-fact-cited-dated-and-disputed-building-nodus-on-falkordb-14" rel="nofollow"&gt;https://dev.to/rishik92/every-fact-cited-dated-and-disputed-building-nodus-on-falkordb-14&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The demo company is fully synthetic, generated by &lt;code&gt;world/build.py&lt;/code&gt; and committed under &lt;code&gt;world/seeds/&lt;/code&gt;. No private or personal data is used.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How Nodus covers the Track 3 focus areas&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Unified ingestion across docs, tickets, and chats.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Multi-format connectors normalize design docs, issue tickets, Markdown files, and Slack exports into a uniform provenance subgraph: &lt;code&gt;Source&lt;/code&gt; nodes…&lt;/p&gt;
&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/RISHIK92/graph-hackathon" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Nodus was built for &lt;strong&gt;Track 03 (Company Brain)&lt;/strong&gt; of the FalkorDB hackathon. If you've fought the "who owns this?" problem at your company, I'd love to hear how. Find me at &lt;a href="https://github.com/RISHIK92" rel="noopener noreferrer"&gt;https://github.com/RISHIK92&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  FalkorDB docs referenced in this post
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.falkordb.com/algorithms" rel="noopener noreferrer"&gt;Graph algorithms overview&lt;/a&gt;: &lt;a href="https://docs.falkordb.com/algorithms/pagerank" rel="noopener noreferrer"&gt;PageRank&lt;/a&gt;, &lt;a href="https://docs.falkordb.com/algorithms/betweenness-centrality" rel="noopener noreferrer"&gt;Betweenness centrality&lt;/a&gt;, &lt;a href="https://docs.falkordb.com/algorithms/wcc" rel="noopener noreferrer"&gt;WCC&lt;/a&gt;, &lt;a href="https://docs.falkordb.com/algorithms/sppath" rel="noopener noreferrer"&gt;algo.SPpaths&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.falkordb.com/cypher/indexing/vector-index" rel="noopener noreferrer"&gt;Vector indexing&lt;/a&gt; and &lt;a href="https://docs.falkordb.com/cypher/indexing/fulltext-index" rel="noopener noreferrer"&gt;full-text indexing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.falkordb.com/commands/graph.constraint-create" rel="noopener noreferrer"&gt;GRAPH.CONSTRAINT CREATE&lt;/a&gt; and the &lt;a href="https://docs.falkordb.com/cypher/foreach" rel="noopener noreferrer"&gt;FOREACH clause&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.falkordb.com/design/concurrency" rel="noopener noreferrer"&gt;Atomicity and concurrency control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.falkordb.com/design" rel="noopener noreferrer"&gt;FalkorDB architecture and design&lt;/a&gt; (sparse adjacency matrices and GraphBLAS)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.falkordb.com/cypher/match" rel="noopener noreferrer"&gt;MATCH clause&lt;/a&gt; (including variable-length patterns)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.falkordb.com/operations/docker" rel="noopener noreferrer"&gt;Running FalkorDB with Docker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>falkordb</category>
      <category>graphdatabase</category>
      <category>ai</category>
      <category>wemakedevs</category>
    </item>
  </channel>
</rss>
