<?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: DAN HE</title>
    <description>The latest articles on DEV Community by DAN HE (@dhlisa).</description>
    <link>https://dev.to/dhlisa</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%2F4191324%2Fca69de20-c722-4d2b-a333-4b551aa7117d.png</url>
      <title>DEV Community: DAN HE</title>
      <link>https://dev.to/dhlisa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dhlisa"/>
    <language>en</language>
    <item>
      <title>HandbookBrain: a company brain that knows why every rule exists (built on FalkorDB)</title>
      <dc:creator>DAN HE</dc:creator>
      <pubDate>Sun, 18 Oct 2026 01:44:52 +0000</pubDate>
      <link>https://dev.to/dhlisa/handbookbrain-a-company-brain-that-knows-why-every-rule-exists-built-on-falkordb-1m89</link>
      <guid>https://dev.to/dhlisa/handbookbrain-a-company-brain-that-knows-why-every-rule-exists-built-on-falkordb-1m89</guid>
      <description>&lt;p&gt;Meet Maya. She's three weeks into Marketing Ops, and her manager asks: &lt;em&gt;why did trial MQLs drop at the end of July?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So she starts digging. Forty chat threads. A 58-minute meeting recording. The handbook page, edited twice. An old PDF on the shared drive that still says 25 points. The lead score field in the CRM. A dashboard. A merge request. Seven tools later, she still isn't sure who decided what, or why.&lt;/p&gt;

&lt;p&gt;This is the problem I built &lt;strong&gt;HandbookBrain&lt;/strong&gt; for, for the &lt;a href="https://www.wemakedevs.org/hackathons/falkordb" rel="noopener noreferrer"&gt;Graph Hacks&lt;/a&gt; hackathon (Track 3: Company Brain).&lt;/p&gt;

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

&lt;p&gt;Code: &lt;a href="https://github.com/danhe6/handbookbrain" rel="noopener noreferrer"&gt;github.com/danhe6/handbookbrain&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: decisions move faster than documents
&lt;/h2&gt;

&lt;p&gt;Business rules change all the time. A lead-scoring threshold moves, a Deal Desk check is added, a partner renewal rule flips. The decision is made in a meeting or a Slack thread. The document is updated weeks later, or never. And everything that relied on the old rule quietly goes wrong: answers people were given, other docs, CRM fields, dashboards.&lt;/p&gt;

&lt;p&gt;Search doesn't fix this. Search finds a document. Maya's question is a &lt;em&gt;path&lt;/em&gt;: meeting → decision → who decided → the change that implemented it → the section version that changed → everything that depended on it.&lt;/p&gt;

&lt;p&gt;That is a graph problem, so I built it as one.&lt;/p&gt;

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

&lt;p&gt;HandbookBrain keeps one &lt;strong&gt;versioned knowledge graph&lt;/strong&gt; in FalkorDB. It holds a company's documents, the meetings and chats where decisions are made, the merge requests that implement them, and the systems, CRM fields, metrics and dashboards those documents touch. An agent works on top of it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Answers "how does X work, and why?"&lt;/strong&gt; with a citation to the exact &lt;em&gt;version&lt;/em&gt; of each section it used, plus the trail back to the meeting, the person who decided and the merge request that wrote it down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detects what a change breaks.&lt;/strong&gt; When a section changes, it finds every stored answer that cited the old version, judges from the diff whether the answer is now wrong, notifies the page owners and re-answers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Closes the gap between decision and documentation.&lt;/strong&gt; A decision that no document reflects after three weeks gets a drafted update, routed to the page owner to approve.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The demo uses GitLab's public go-to-market handbook (Marketing Ops, Deal Desk, Sales Systems, Channel Ops, Marketing Analytics) and replays &lt;strong&gt;74 real merged MRs&lt;/strong&gt; from April to October 2026 in date order. The meetings and Slack threads around those MRs are synthetic: I generated them with an LLM, anchored to the real changes, and every synthetic file is labelled as such.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data model
&lt;/h2&gt;

&lt;p&gt;Three ideas carry the whole design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Version sections, not documents.&lt;/strong&gt; A page is split into sections. Every change creates a new &lt;code&gt;Section&lt;/code&gt; node that &lt;code&gt;SUPERSEDES&lt;/code&gt; the previous one, with &lt;code&gt;valid_from&lt;/code&gt; / &lt;code&gt;valid_to&lt;/code&gt;. Nothing is overwritten, so you can read the graph &lt;em&gt;as of any date&lt;/em&gt;, and a rule that was deleted is still findable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Answers are nodes.&lt;/strong&gt; An &lt;code&gt;Answer&lt;/code&gt; &lt;code&gt;CITES&lt;/code&gt; the exact section versions it used. That one edge is what lets a later change find the answers it breaks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Decisions are first-class.&lt;/strong&gt; Each decision is extracted from a meeting, chat or ticket with the speaker and a verbatim quote, linked to the section it is about, and reconciled to the commit that implemented it by reading the diff.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;&lt;span class="nv"&gt;Conversation&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;-DECIDED_IN- Decision -DECIDED_BY-&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Person&lt;/span&gt;
&lt;span class="nv"&gt;Decision&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;IMPLEMENTED_BY&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Commit&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;-MERGED_AS- MergeRequest
Commit -CHANGED-&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Section&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;SUPERSEDES&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;older&lt;/span&gt; &lt;span class="nv"&gt;Section&lt;/span&gt;
&lt;span class="nv"&gt;Page&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;HAS_SECTION&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Section&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;MENTIONS&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;System&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;Field&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;Metric&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;Dashboard&lt;/span&gt;
&lt;span class="nv"&gt;Answer&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;CITES&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Section&lt;/span&gt;     &lt;span class="nv"&gt;Answer&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;ANSWERS&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Question&lt;/span&gt;
&lt;span class="nv"&gt;Answer&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;INVALIDATED_BY&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="nv"&gt;REVALIDATED_BY&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Commit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Cypher that does the work
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"Why is it like this?"&lt;/strong&gt; is one path from a section back to the meeting:&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="w"&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;Section&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;sid:&lt;/span&gt;&lt;span class="n"&gt;$sid&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;:CHANGED&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;c:&lt;/span&gt;&lt;span class="k"&gt;Commit&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;:IMPLEMENTED_BY&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;d:&lt;/span&gt;&lt;span class="n"&gt;Decision&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;:DECIDED_IN&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;cv:&lt;/span&gt;&lt;span class="n"&gt;Conversation&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;OPTIONAL&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;d&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;:DECIDED_BY&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;by:&lt;/span&gt;&lt;span class="n"&gt;Person&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;d.statement&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d.decided_at&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv.title&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;meeting&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;by.name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;decided_by&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c.ts&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;implemented_at&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;"Which answers did this change break?"&lt;/strong&gt; starts from the commit, steps back to the version each answer cited, and returns the old and new text for a diff-based judge:&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="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;c:&lt;/span&gt;&lt;span class="k"&gt;Commit&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;sha:&lt;/span&gt;&lt;span class="n"&gt;$sha&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;ch:&lt;/span&gt;&lt;span class="n"&gt;CHANGED&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;s:&lt;/span&gt;&lt;span class="n"&gt;Section&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;OPTIONAL&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;s&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;:SUPERSEDES&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;prev:&lt;/span&gt;&lt;span class="n"&gt;Section&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;ch&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;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;ch.kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'removed'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;cited&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;cited&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="ow"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&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="py"&gt;a:&lt;/span&gt;&lt;span class="n"&gt;Answer&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;status:&lt;/span&gt;&lt;span class="s1"&gt;'fresh'&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;:CITES&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;cited&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;a.id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ch.kind&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cited.text&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;old_text&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s.text&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;new_text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;"What else relies on what changed?"&lt;/strong&gt; goes from a change to the fields and metrics it mentions, then to every other current document and its owners:&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="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:Commit&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;sha:&lt;/span&gt;&lt;span class="n"&gt;$sha&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;:CHANGED&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;s:&lt;/span&gt;&lt;span class="n"&gt;Section&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;:MENTIONS&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;e&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;e&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;:MENTIONS&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;o:&lt;/span&gt;&lt;span class="n"&gt;Section&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;:HAS_SECTION&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;p:&lt;/span&gt;&lt;span class="n"&gt;Page&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;o.valid_to&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;o.path&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s.path&lt;/span&gt;
&lt;span class="k"&gt;OPTIONAL&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;p&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;:OWNED_BY&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;owner&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="nf"&gt;labels&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="ss"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&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;type&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e.name&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p.title&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;owner.handle&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;owners&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Retrieval is hybrid, all inside FalkorDB: a &lt;strong&gt;vector index&lt;/strong&gt; on section embeddings plus a &lt;strong&gt;full-text index&lt;/strong&gt; on heading and text, fused with reciprocal-rank fusion and filtered to current versions. Having vectors, full-text and graph traversal in one database meant there was no second store to keep in sync.&lt;/p&gt;

&lt;p&gt;The agent itself is a tool-calling loop over 10 parameterised, read-only Cypher tools. Every claim must carry a citation that a tool actually returned, which is checked in code. A reviewer running on a different model sees only the question, the evidence and the draft, and can send the draft back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maya's question, answered
&lt;/h2&gt;

&lt;p&gt;Here is how the trial-threshold story plays out in the replay:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;May 21&lt;/strong&gt;: real MR !19600 adds &lt;em&gt;Trial Threshold Scoring&lt;/em&gt; to the Marketo page. HandbookBrain links it to a (synthetic) Marketing Ops meeting 13 days earlier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jun 1&lt;/strong&gt;: a new analyst asks "At how many points does a trial user MQL?" The answer is &lt;strong&gt;25 points&lt;/strong&gt;, citing that section version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jul 17&lt;/strong&gt;: the (synthetic) Marketing Ops weekly decides to raise it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jul 23&lt;/strong&gt;: real MR !20534 changes &lt;code&gt;25&lt;/code&gt; → &lt;code&gt;31&lt;/code&gt;. The Jun 1 answer is judged &lt;strong&gt;stale&lt;/strong&gt; from the diff, the owners from CODEOWNERS are notified, and the answer is refreshed to &lt;strong&gt;31&lt;/strong&gt;, with the meeting, the person and the MR attached.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Maya asks one question and gets the whole path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking the graph against independent sources
&lt;/h2&gt;

&lt;p&gt;An answer engine you can't verify is a liability, so I wrote an audit that checks what the site shows against sources the agent never reads:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Source of truth&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;Change dates, MR numbers, version chains&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git log&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2562 / 2562&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who decided, where, the quote&lt;/td&gt;
&lt;td&gt;meeting transcripts&lt;/td&gt;
&lt;td&gt;21 / 21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document owners&lt;/td&gt;
&lt;td&gt;CODEOWNERS&lt;/td&gt;
&lt;td&gt;122 / 122&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decision → implementing MR&lt;/td&gt;
&lt;td&gt;hidden labels&lt;/td&gt;
&lt;td&gt;0 wrong links&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The audit earned its keep. My first decision-to-MR judge was too lenient and linked three decisions to later, unrelated changes. Now only what actually changed in the diff counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluating the agent, honestly
&lt;/h2&gt;

&lt;p&gt;This is the part I learned the most from.&lt;/p&gt;

&lt;p&gt;My first headline was "89% vs 0%" on &lt;em&gt;why&lt;/em&gt; questions against a plain RAG baseline. It looked great, and it wasn't fair. When I checked fact by fact, plain retrieval found the meeting date and the person who decided &lt;strong&gt;as well as my agent did&lt;/strong&gt;. It scored 0% only because the merge request wasn't in its data at all.&lt;/p&gt;

&lt;p&gt;So I rebuilt the eval, following Anthropic's &lt;a href="https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents" rel="noopener noreferrer"&gt;Demystifying evals for AI agents&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;code graders where possible, and a model judge only for open answers;&lt;/li&gt;
&lt;li&gt;questions that should be answered and questions that should be declined;&lt;/li&gt;
&lt;li&gt;3 trials per question, with the LLM cache off.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also added a stronger baseline, &lt;strong&gt;RAG + git log&lt;/strong&gt;, which gets the whole merge log as text.&lt;/p&gt;

&lt;p&gt;29 questions × 3 trials:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;HandbookBrain&lt;/th&gt;
&lt;th&gt;RAG + git log&lt;/th&gt;
&lt;th&gt;Plain RAG&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;All questions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;87%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;66%&lt;/td&gt;
&lt;td&gt;59%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Names the MR that implemented a decision&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;18 / 18&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4 / 18&lt;/td&gt;
&lt;td&gt;0 / 18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Old / new value of a changed rule&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15 / 15&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;9 / 15&lt;/td&gt;
&lt;td&gt;6 / 15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meeting date of the decision&lt;/td&gt;
&lt;td&gt;18 / 18&lt;/td&gt;
&lt;td&gt;18 / 18&lt;/td&gt;
&lt;td&gt;18 / 18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plain lookups&lt;/td&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What this says:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finding the meeting is a retrieval problem, and retrieval solves it.&lt;/li&gt;
&lt;li&gt;What the graph adds is the &lt;strong&gt;link from a decision to the change that implemented it&lt;/strong&gt; and the &lt;strong&gt;version history&lt;/strong&gt;, including rules that were deleted.&lt;/li&gt;
&lt;li&gt;Even with the whole merge log in its context, the model rarely matched a meeting to the right MR on its own.&lt;/li&gt;
&lt;li&gt;On plain lookups the graph adds nothing, and the agent is slower and more expensive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The caveats belong in the post too. The questions are mine, from one dataset, and I fixed the agent after reading its failures on these same questions. Expect lower numbers on new questions.&lt;/p&gt;

&lt;p&gt;The eval found real bugs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A section titled &lt;em&gt;Trial Threshold Scoring&lt;/em&gt; was buried under a generic "MQL and Lead Scoring" section, so the agent answered &lt;strong&gt;100&lt;/strong&gt; instead of &lt;strong&gt;31&lt;/strong&gt;. Fix: heading-match candidates in search.&lt;/li&gt;
&lt;li&gt;Wide markdown tables were misread by column. Fix: rows are linearised.&lt;/li&gt;
&lt;li&gt;Rules deleted from a page were invisible. Fix: search also returns older versions that said more, with the MR that removed them.&lt;/li&gt;
&lt;li&gt;A rule agreed only in Slack was presented as policy, with a handbook citation. Fix: undocumented decisions are now marked "only said in chat", and the agent has to say so.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Make the answer a node.&lt;/strong&gt; Once answers cite exact versions, "what did this change break?" is a traversal, not a guess.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version at the section level.&lt;/strong&gt; Whole-document versions are too coarse to tell which answer a change affects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure per fact, against a fair baseline.&lt;/strong&gt; An aggregate score hid where the graph helps and where it doesn't. The honest table is more convincing than the flattering one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit LLM-built edges against something independent.&lt;/strong&gt; The model that extracts a link should not be the one that grades it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Direct connectors (Slack, Zoom, Drive) instead of exports, and speech-to-text for recordings.&lt;/li&gt;
&lt;li&gt;CRM and BI metadata (Salesforce fields, Power BI / Tableau dashboards) as first-class nodes.&lt;/li&gt;
&lt;li&gt;Opening the merge request for an approved update, not just recording the sign-off.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your team's rules live in five tools and nobody remembers why any of them exist, I'd love to hear how you handle it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built with FalkorDB, Python, FastAPI and LiteLLM. Most of the code was written with an AI coding assistant (Claude) under my direction and review.&lt;/em&gt;&lt;/p&gt;

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