{"slug": "every-fact-cited-dated-and-disputed-building-nodus-on-falkordb", "title": "Every Fact Cited, Dated and Disputed: Building Nodus on FalkorDB", "summary": "A developer built Nodus, an organizational knowledge graph on FalkorDB that models every fact as a versioned, bi-temporal Assertion node linked to the exact character range of its source Span, so answers about ownership, dependencies and decisions cite their evidence. The system keeps an append-only assertion ledger alongside fast traversal edges, letting blast-radius queries run in under a millisecond while resolving each result back to its supporting quote, and it resolves contradictions by only letting a new claim supersede an old one when its authority is equal or higher.", "body_md": "\"Who owns `ledger-service`?\" sounds like a ten-second question. In most companies it takes an afternoon.\n\nThe service catalog says Platform. A design doc from last spring says Payments. A Slack thread from Tuesday says *\"Infra owns Ledger now.\"* The person who would know left in the reorg. Each source is a little bit right, and none of them says **when** it was right.\n\nThe 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, **a confident wrong answer is worse than no answer.**\n\nI wanted a company brain that shows its receipts. That became **Nodus**.\n\nNodus is an organizational knowledge graph built entirely on [FalkorDB](https://www.falkordb.com/). It pulls people, teams, systems, projects, tickets, docs, chats and architecture decisions (ADRs) into one bi-temporal graph. It answers *\"who owns this?\"*, *\"who knows about this?\"* and *\"why was this decided?\"*, and every answer comes with an exact quote from its source.\n\nThe core idea is simple: **a fact is not a chunk of text; it is a versioned claim with evidence.** Every claim Nodus learns becomes an `Assertion` node that points at the exact character range of the `Span` it came from. Each assertion knows:\n\n`valid_from`, `valid_to`)` recorded_at`, `retracted_at`)` 4` structured data or human confirmation, `2` docs, `1` chat, `0` drafts)\nThe two clocks let Nodus answer two different historical questions. *\"Who owned Ledger before the reorg?\"* asks about reality. *\"What did we **believe** 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.\n\nFalkorDB 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.\n\nThe first design fought itself. If every fact is its own node, you keep perfect provenance, but a question like *\"what breaks if Ledger goes down?\"* 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.\n\nI stopped choosing and kept both layers in the same graph:\n\n`Source -[:HAS_SPAN]-> Span <-[:EVIDENCED_BY]- Assertion`, with assertions linked to each other by `SUPERSEDES`, `CONTINUES`, `DISPUTES` and `CONFIRMS`. This layer is append-only and holds the full story.` OWNS`, `LEADS`, `DEPENDS_ON`, `PART_OF`, `AFFECTS`, `REPLACES`). Each edge carries `{aid, valid_from, valid_to, authority}`, where `aid` points back to its assertion.\nQueries walk the fast edges on the right; the dashed `aid` link is how any answer finds its way back to the claim and quote on the left.\n\nOperational queries run on the fast layer and only touch the ledger to fetch citations. Here is the blast-radius query:\n\n```\nMATCH p = (x:Entity {id:$x})<-[:DEPENDS_ON*1..3]-(s:Entity)\nWHERE all(e IN relationships(p) WHERE e.valid_from <= $V AND e.valid_to > $V)\nRETURN s.id, s.name, length(p) AS hops, [e IN relationships(p) | e.aid] AS aids\nORDER BY hops\n```\n\nThat is a [variable-length traversal](https://docs.falkordb.com/cypher/match) with a time filter on every hop, and on the demo graph it returns in under a millisecond on FalkorDB's [sparse adjacency matrices](https://docs.falkordb.com/design). The `aids` it returns resolve straight to the assertions and quoted spans behind each dependency, so **the answer cites its sources at no extra cost.**\n\nReal companies contradict themselves constantly, so contradiction handling had to be a first-class rule rather than an edge case. Nodus uses one rule: **a new claim only replaces an old one if its authority is equal or higher.**\n\nThe demo shows this end to end. The catalog (authority `4`) says Payments owns Ledger. Someone posts *\"Infra owns Ledger now\"* in Slack (authority `1`). 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 `4`) supersedes the old fact and Infra becomes the owner, with the full chain of evidence intact.\n\nThe same ledger powers **stale-doc detection**. 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.\n\nA rule like this is easy to get subtly wrong, so I tested it with **property-based tests**. 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.\n\nSome 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 [native GraphBLAS implementations](https://docs.falkordb.com/algorithms) of exactly the algorithms for these, and they are fast.\n\nThe catch is that they filter by **node label and relationship type**, not by property predicates evaluated per hop. I couldn't pass `valid_from <= $V < valid_to` into PageRank, and running on the live graph would mix in edges from every era.\n\nSo Nodus builds **snapshot graphs**. For a timestamp `$V`, it materializes a lightweight read-only graph named `<graph>__snap_<YYYYMMDD>` that contains only the edges valid at that moment, plus pre-aggregated helper edges. The native algorithms run on the snapshot:\n\n| Question | FalkorDB procedure | \n|---|---|\n| Which systems are most critical? | [`algo.pageRank('System', 'DEPENDS_ON')`](https://docs.falkordb.com/algorithms/pagerank) | \n| Who are our single points of failure? | [`algo.betweenness`](https://docs.falkordb.com/algorithms/betweenness-centrality) over undirected`LINK` edges | \n| Where are our knowledge gaps and silos? | [`algo.WCC`](https://docs.falkordb.com/algorithms/wcc) over` OWNS` ,`LEADS` ,`MEMBER_OF` ,`PART_OF` | \n| How is Bob connected to Ledger? | [`algo.SPpaths`](https://docs.falkordb.com/algorithms/sppath) , top 3 paths, up to 4 hops, both directions | \n\nAlgorithm output is treated as a **ranking, not a claim**. 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.\n\nNodus 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.\n\nThe usual pattern is to check permissions in application code, then write. That leaves a **time-of-check to time-of-use (TOCTOU)** gap. Between the check and the write, ownership might change, or someone else might settle the same dispute.\n\nThe fix was to **move the permission check into the write**. Every mutation is a single Cypher query. It first checks eligibility against the org graph itself (`Person -[:LEADS]-> Team -[:OWNS]-> System`) and confirms the target assertion is still in the state the caller saw. That leaves one question: *how do you make a Cypher query abort halfway on purpose?*\n\nThe answer is a unique constraint and a sentinel. At setup, Nodus creates a [unique constraint](https://docs.falkordb.com/commands/graph.constraint-create) on `:GuardFail(id)` and seeds one node, `{id: 'sentinel'}`. Each write query includes a [` FOREACH`](https://docs.falkordb.com/cypher/foreach) guard:\n\n```\nFOREACH (_ IN CASE WHEN ok THEN [] ELSE [1] END |\n  CREATE (:GuardFail {id:'sentinel'}))\n```\n\nIf 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. **The permission check, the state change, the edge-cache update and the `Action` audit node succeed or fail together.**\n\nThis relies on two guarantees FalkorDB documents in [Atomicity and Concurrency Control](https://docs.falkordb.com/design/concurrency): 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.\n\nThose `Action` nodes enable one of my favorite features. Each records what it was `BASED_ON` and what it `PRODUCED`, so Nodus can show any past action side by side: **what was true when it was taken, and what is true now.**\n\nIn 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. **It never decides what is true.**\n\nEvery answer passes a **deterministic verifier** before it reaches the user. For each claim in the draft, the verifier checks against FalkorDB that:\n\nIf the graph lacks evidence for something, Nodus says what is missing instead of filling the gap. *\"I can't find who owns this after the reorg\"* is a useful answer. A plausible guess is not.\n\nThis design has a nice side effect: Nodus runs **fully offline**. 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 `Entity` and `Span` nodes, so [vector search](https://docs.falkordb.com/cypher/indexing/vector-index) (`db.idx.vector.queryNodes` over `vecf32`) and [full-text search](https://docs.falkordb.com/cypher/indexing/fulltext-index) (`db.idx.fulltext.queryNodes`) are fused with graph expansion in the same engine.\n\nThe same read and guarded-write tools are also exposed over **MCP**, so a coding agent like Claude Code or Cursor can ask *\"who should review my change to ledger-service?\"* from the IDE and get the same cited answer.\n\nI 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.\n\nThe 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 `aid` on every edge is the small detail that makes it work.\n\nI 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.\n\nAuthorization 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 **impossible rather than unlikely**.\n\nThe 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.\n\nConflict 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.\n\nForcing 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.\n\nAsk it *\"Who owns ledger-service, and what depends on it?\"*, *\"Why did we adopt ADR-007?\"* or *\"Who are our single points of failure?\"* Then drag the time slider back before the reorg and ask again. The demo company is fully synthetic, so feel free to break things.\n\nTo run it locally, start FalkorDB with Docker (`docker run -p 6379:6379 falkordb/falkordb-server`; see the [Docker guide](https://docs.falkordb.com/operations/docker)), seed the demo company with `brain reset --seed world/seeds/1`, 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.\n\n**Track entered: Track 03, Company Brain**\n\nNodus 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.\n\n**Demo video:** [https://youtu.be/E35AO0AtxxY](https://youtu.be/E35AO0AtxxY)\n\n**Live deployment:** [https://nodus.rishiklabs.tech/](https://nodus.rishiklabs.tech/)\n\nThe demo company is fully synthetic, generated by `world/build.py` and committed under `world/seeds/`. No private or personal data is used.\n\n**Unified ingestion across docs, tickets, and chats.**\n\nMulti-format connectors normalize design docs, issue tickets, Markdown files, and Slack exports into a uniform provenance subgraph: `Source` nodes segmented into…\n\nNodus was built for **Track 03 (Company Brain)** 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 [https://github.com/RISHIK92](https://github.com/RISHIK92).", "url": "https://wpnews.pro/news/every-fact-cited-dated-and-disputed-building-nodus-on-falkordb", "canonical_source": "https://dev.to/rishik92/every-fact-cited-dated-and-disputed-building-nodus-on-falkordb-14", "published_at": "2026-10-18 16:37:56+00:00", "updated_at": "2026-10-18 16:47:28.531571+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-agents", "ai-tools", "mlops", "developer-tools"], "entities": ["Nodus", "FalkorDB"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/every-fact-cited-dated-and-disputed-building-nodus-on-falkordb", "markdown": "https://wpnews.pro/news/every-fact-cited-dated-and-disputed-building-nodus-on-falkordb.md", "text": "https://wpnews.pro/news/every-fact-cited-dated-and-disputed-building-nodus-on-falkordb.txt", "jsonld": "https://wpnews.pro/news/every-fact-cited-dated-and-disputed-building-nodus-on-falkordb.jsonld"}}