<?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: Petru</title>
    <description>The latest articles on DEV Community by Petru (@petsteb).</description>
    <link>https://dev.to/petsteb</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%2F1562865%2F8de1f476-5ecf-49d7-9d97-39541397cce7.jpg</url>
      <title>DEV Community: Petru</title>
      <link>https://dev.to/petsteb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/petsteb"/>
    <language>en</language>
    <item>
      <title>What happens when someone leaves? Building a company brain that shows its work</title>
      <dc:creator>Petru</dc:creator>
      <pubDate>Sun, 18 Oct 2026 18:24:18 +0000</pubDate>
      <link>https://dev.to/petsteb/what-happens-when-someone-leaves-building-a-company-brain-that-shows-its-work-2p4m</link>
      <guid>https://dev.to/petsteb/what-happens-when-someone-leaves-building-a-company-brain-that-shows-its-work-2p4m</guid>
      <description>&lt;p&gt;&lt;em&gt;Lore, our Graph Hacks 2026 entry (Track 03, Company Brain), answers "what breaks if she leaves?" on FalkorDB, with the graph path and source behind every line.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When an engineer hands in their notice, the useful questions are not about her profile. They are about what is attached to her. Which issues are still assigned to her, and which customers are waiting on them? Is she the only person who has ever fixed some component? Who could pick up each piece? Which of her meeting promises still stand?&lt;/p&gt;

&lt;p&gt;None of those answers live in a single record. They live in how records connect: an issue to a component, a component to the same kind of component in another product, a commitment to the later meeting that changed it. We built Lore, a company brain on FalkorDB, to answer them under one rule: every answer shows its work. Lore returns the graph path behind each row, the source messages it rests on, and the exact Cypher that ran.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data: a synthetic company
&lt;/h2&gt;

&lt;p&gt;We built on &lt;a href="https://huggingface.co/datasets/Salesforce/HERB" rel="noopener noreferrer"&gt;HERB&lt;/a&gt;, an enterprise benchmark from Salesforce Research, licensed CC-BY-NC-4.0. It is &lt;strong&gt;synthetic&lt;/strong&gt;: no real people or private data. It models one company with 30 products, 530 employees, an org chart, 120 customers, Slack channels, documents, meeting transcripts and pull requests.&lt;/p&gt;

&lt;p&gt;The loader rebuilds the graph at startup in about 5 seconds: roughly 40,000 nodes and 108,000 relationships, including 745 issues (309 open, 59 reopened, 377 resolved) across 198 components, 33,632 Slack messages and 89 decisions and commitments extracted from meetings. One Docker image runs FalkorDB &lt;code&gt;v4.22.1&lt;/code&gt; (pinned) and the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  The graph model, briefly
&lt;/h2&gt;

&lt;p&gt;The core labels are &lt;code&gt;Person&lt;/code&gt;, &lt;code&gt;Product&lt;/code&gt;, &lt;code&gt;Component&lt;/code&gt;, &lt;code&gt;Issue&lt;/code&gt;, &lt;code&gt;Customer&lt;/code&gt;, &lt;code&gt;Document&lt;/code&gt;, &lt;code&gt;Meeting&lt;/code&gt;, &lt;code&gt;Decision&lt;/code&gt; and &lt;code&gt;Message&lt;/code&gt;, connected by edges such as &lt;code&gt;ASSIGNED_TO&lt;/code&gt;, &lt;code&gt;IN_COMPONENT&lt;/code&gt;, &lt;code&gt;AFFECTS&lt;/code&gt;, &lt;code&gt;OWNED_BY&lt;/code&gt; and &lt;code&gt;DECIDED_IN&lt;/code&gt;. Issue lifecycle events (&lt;code&gt;REPORTED_IN&lt;/code&gt;, &lt;code&gt;RESOLVED_IN&lt;/code&gt;, &lt;code&gt;REOPENED_IN&lt;/code&gt;) point at the Slack message that recorded them.&lt;/p&gt;

&lt;p&gt;Two derived edges do most of the interesting work. &lt;code&gt;RESOLVED&lt;/code&gt; links a person to an issue when they posted its resolution. &lt;code&gt;SAME_KIND_AS&lt;/code&gt; links same-named components across products (&lt;code&gt;WHERE a.name = b.name AND a.id &amp;lt; b.id AND a.name &amp;lt;&amp;gt; 'Unspecified'&lt;/code&gt;), so every product's "API Authentication Layer" is connected to the others.&lt;/p&gt;

&lt;p&gt;The agent never writes Cypher. Lore has a fixed catalogue of tools, each with a hand-written, parameterised query, exposed over MCP and a REST API. The model picks a tool and fills in arguments, and every entity argument is resolved against the graph first. Memory is more nodes: &lt;code&gt;remember&lt;/code&gt; writes a &lt;code&gt;Finding&lt;/code&gt; linked &lt;code&gt;ABOUT&lt;/code&gt; an entity, and &lt;code&gt;recall&lt;/code&gt; returns findings with their dates.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hero question at depth 1 and depth 3
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;if_person_leaves(person, depth)&lt;/code&gt; is the tool we built the demo around. At depth 1 it asks for open work: issues currently assigned to the person that are not resolved.&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;a:&lt;/span&gt;&lt;span class="n"&gt;Person&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;$pid&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="py"&gt;asg:&lt;/span&gt;&lt;span class="n"&gt;ASSIGNED_TO&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;i:&lt;/span&gt;&lt;span class="n"&gt;Issue&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;:IN_COMPONENT&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;c:&lt;/span&gt;&lt;span class="n"&gt;Component&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;i.status&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'resolved'&lt;/span&gt; &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;asg.until&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;i.id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i.title&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i.status&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c.id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;component&lt;/span&gt;&lt;span class="ss"&gt;,&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;...&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;path&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&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="nf"&gt;type&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&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;rels&lt;/span&gt;  &lt;span class="c1"&gt;// node projection trimmed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the engineer &lt;code&gt;eid_92c62291&lt;/code&gt; (one of four Alice Browns, more on that below), depth 1 returns &lt;strong&gt;7 open issues and 4 meeting commitments&lt;/strong&gt;. Each row carries its graph path, and each commitment carries its verbatim meeting quote. A join could have produced most of that.&lt;/p&gt;

&lt;p&gt;Depth 2 adds the customers waiting on her issues, one entry per distinct customer, and any component where she is the only person who has ever resolved anything. Depth 3 (which includes depth 2) adds possible successors. Compared with depth 1, the answer gains &lt;strong&gt;6 waiting customers&lt;/strong&gt; and, at depth 3, &lt;strong&gt;7 possible successors&lt;/strong&gt;. She has no sole-expert component. The check exists, but it skips the generic &lt;code&gt;Unspecified&lt;/code&gt; component, because a bucket for issues the parser could not place says nothing about expertise. The successor query is the one a simple join would not give us:&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;a:&lt;/span&gt;&lt;span class="n"&gt;Person&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;$pid&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="py"&gt;asg:&lt;/span&gt;&lt;span class="n"&gt;ASSIGNED_TO&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;i:&lt;/span&gt;&lt;span class="n"&gt;Issue&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;:IN_COMPONENT&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;c:&lt;/span&gt;&lt;span class="n"&gt;Component&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;i.status&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'resolved'&lt;/span&gt; &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;asg.until&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&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;a&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;:ASSIGNED_TO&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="n"&gt;i&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;:IN_COMPONENT&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;c&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;:SAME_KIND_AS&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="m"&gt;1&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;c2:&lt;/span&gt;&lt;span class="n"&gt;Component&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;:IN_COMPONENT&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;:Issue&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;:RESOLVED&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;Person&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;s.id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;$pid&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;i.title&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s.id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;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="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;count&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="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;fixes&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;...&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;fixes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SAME_KIND_AS*0..1&lt;/code&gt; is the important part. Zero hops means "someone who fixed this component". One hop means "someone on another product who fixed the same kind of component", which finds a successor even when nobody on her team has touched it. Each candidate comes with the path to the fix it rests on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Alice? Refusing to guess
&lt;/h2&gt;

&lt;p&gt;Ask "who is affected if Alice Brown leaves?" and Lore does not answer. It returns &lt;code&gt;ambiguous_entity&lt;/code&gt;, because four employees are named Alice Brown: a VP of Engineering, two software engineers and a QA specialist. It lists each one with role and products, and asks you to pick by id.&lt;/p&gt;

&lt;p&gt;A name that matches more than one person stops the tool before any traversal runs. An unknown name stops it too: ask &lt;code&gt;who_knows&lt;/code&gt; about the "Quantum Flux Capacitor" and you get &lt;code&gt;unknown_entity&lt;/code&gt;, and the trace shows only the name lookup. A name that exists with nothing connected gets a third outcome, &lt;code&gt;not_in_graph&lt;/code&gt;, worded as "the knowledge isn't recorded, not that nobody has it". The optional Claude front end adds one check: if its reply names an &lt;code&gt;eid_&lt;/code&gt; or &lt;code&gt;CUST-&lt;/code&gt; id that no tool returned, the reply is replaced by the tools' own summaries.&lt;/p&gt;

&lt;p&gt;The same idea applies at load time. Transcripts name speakers by display name, so the decision loader resolves a name only among that meeting's participants. When two participants share a name, it links both and marks the edges &lt;code&gt;ambiguous: true&lt;/code&gt; (60 links in the dataset). All of this Alice's commitment edges resolved cleanly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping knowledge current without overwriting it
&lt;/h2&gt;

&lt;p&gt;A company brain goes stale if "current" is a field someone must remember to update. In Lore nothing is overwritten, and "current" is derived from history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issue status&lt;/strong&gt; comes from the latest lifecycle event:&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;i:&lt;/span&gt;&lt;span class="n"&gt;Issue&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;e:&lt;/span&gt;&lt;span class="n"&gt;REPORTED_IN&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;RESOLVED_IN&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;REOPENED_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;m:&lt;/span&gt;&lt;span class="n"&gt;Message&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;i&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="n"&gt;m&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;m.ts&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;i&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="nf"&gt;type&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;last&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;i.status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'RESOLVED_IN'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'resolved'&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'REOPENED_IN'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'reopened'&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'open'&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Slow Response Time During Peak Usage" on InsightForce was resolved on August 26 and reopened on August 31, so it is &lt;code&gt;reopened&lt;/code&gt;, and &lt;code&gt;issue_history&lt;/code&gt; shows both messages in order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documents&lt;/strong&gt; are chained per type with &lt;code&gt;SUPERSEDES&lt;/code&gt;. The current version is the one nothing supersedes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decisions&lt;/strong&gt; are current unless a later one &lt;code&gt;REVISES&lt;/code&gt; them. This is why Alice shows 4 commitments, not 7. Three of her seven, including the "Optimize Database Schema for Real-Time Processing" PR, were handed to Bob Martinez in later ContextForce meetings. The commitments query filters on &lt;code&gt;OPTIONAL MATCH (later:Decision)-[:REVISES]-&amp;gt;(t) ... WHERE later IS NULL&lt;/code&gt;, so those three drop out and nothing is deleted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assignments&lt;/strong&gt; carry an &lt;code&gt;until&lt;/code&gt; property. An open assignment is one without it, which is why both queries above check &lt;code&gt;asg.until IS NULL&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provenance: every edge says how it was made
&lt;/h2&gt;

&lt;p&gt;Most of the graph comes from deterministic parsing in &lt;code&gt;lore/herb.py&lt;/code&gt;: &lt;code&gt;eid_&lt;/code&gt; mentions, &lt;code&gt;CUST-&lt;/code&gt; ids, document and PR links, and the fixed phrasing HERB uses in bug-triage channels. No LLM is involved in that part.&lt;/p&gt;

&lt;p&gt;Resolution messages were the hard case. 191 quote the issue title. Another 229 paraphrase it, so we match them against the open issues assigned to the sender in that channel: if there is exactly one, that is the match (84 messages); if there are several, we take the one with the most word overlap and require at least two shared words (145). Every lifecycle edge records its rule in a &lt;code&gt;match&lt;/code&gt; property (&lt;code&gt;quoted-title&lt;/code&gt;, &lt;code&gt;assignee-only-open-issue&lt;/code&gt; or &lt;code&gt;assignee+overlap&lt;/code&gt;), and &lt;code&gt;issue_history&lt;/code&gt; returns it with each event. &lt;strong&gt;70 resolutions and 13 reopens could not be matched, and we left them out&lt;/strong&gt; rather than guess. The result is 420 &lt;code&gt;RESOLVED_IN&lt;/code&gt; edges.&lt;/p&gt;

&lt;p&gt;Decisions and commitments are the only LLM-extracted content in the loaded graph. Claude extracted them offline from the transcripts of three products, each with a verbatim quote. At load time the check is a plain substring test, &lt;code&gt;d["quote"] not in m["transcript"]&lt;/code&gt;, and failing items are rejected. Of 89 items, 0 were rejected, and the quote is what the UI shows as the source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting work done, with a human in the loop
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;plan_handoff&lt;/code&gt; drafts a reassignment for each open issue, picking the colleague with the strongest graph evidence (same product team first, then most fixes in this or a same-kind component). It writes a &lt;code&gt;proposed&lt;/code&gt; &lt;code&gt;Action&lt;/code&gt; node and nothing else. Only &lt;code&gt;apply_action(action_id, approved_by)&lt;/code&gt; changes assignments, and the agent's instructions forbid calling it without explicit approval of a specific action id. Old assignments are closed, not deleted:&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;UNWIND&lt;/span&gt; &lt;span class="n"&gt;$steps&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;s&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;i:&lt;/span&gt;&lt;span class="n"&gt;Issue&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;s.issue&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;old:&lt;/span&gt;&lt;span class="n"&gt;ASSIGNED_TO&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;Person&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;old.until&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;prev.id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s.to&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;old.until&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$ts&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old.ended_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A second query creates the new &lt;code&gt;ASSIGNED_TO&lt;/code&gt; edges with &lt;code&gt;via&lt;/code&gt; and &lt;code&gt;approved_by&lt;/code&gt;, and links the &lt;code&gt;Action&lt;/code&gt; to each issue it touched. For Alice, the plan proposes owners for all 7 open issues (Charlie Taylor, Charlie Davis and Julia Taylor among them). Re-run "if Alice leaves" after approval and &lt;strong&gt;her open issues go from 7 to 0&lt;/strong&gt;. Her 4 meeting commitments remain, on purpose: the tool only hands off issues, and a promise made in a meeting has to be reassigned by a person.&lt;/p&gt;

&lt;h2&gt;
  
  
  What FalkorDB taught us
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A variable-length bound can't be a parameter.&lt;/strong&gt; &lt;code&gt;[:REPORTS_TO*1..$d]&lt;/code&gt; fails with "Encountered unhandled type in inlined properties." We clamp depth to 1..3 in Python and let it choose which fixed queries run, so it never enters the query text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;shortestPath()&lt;/code&gt; is directed-only.&lt;/strong&gt; An undirected pattern returns "FalkorDB does not currently support undirected shortestPath traversals". &lt;code&gt;connect(a, b)&lt;/code&gt; uses the algorithm procedure instead:
&lt;/li&gt;
&lt;/ul&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="n"&gt;x&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;$a&lt;/span&gt;&lt;span class="ss"&gt;}),&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&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;$b&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;algo.SPpaths&lt;/span&gt;&lt;span class="ss"&gt;({&lt;/span&gt;&lt;span class="py"&gt;sourceNode:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="py"&gt;targetNode:&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="py"&gt;relTypes:&lt;/span&gt; &lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="ss"&gt;],&lt;/span&gt; &lt;span class="py"&gt;relDirection:&lt;/span&gt; &lt;span class="s1"&gt;'both'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;pathCount&lt;/span&gt;&lt;span class="dl"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;maxLen&lt;/span&gt;&lt;span class="dl"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;YIELD&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Check the image defaults.&lt;/strong&gt; &lt;code&gt;falkordb/falkordb&lt;/code&gt; ships with &lt;code&gt;FALKORDB_ARGS=MAX_QUEUED_QUERIES 25 TIMEOUT 1000 RESULTSET_SIZE 10000&lt;/code&gt;: one second for read queries and 10,000 rows per result. We set our own limits in the Dockerfile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full-text search uses RediSearch syntax.&lt;/strong&gt; Punctuation in user text can be read as operators, so every string goes through an &lt;code&gt;ft()&lt;/code&gt; helper that replaces &lt;code&gt;[^\w\s]&lt;/code&gt; with spaces before it reaches &lt;code&gt;db.idx.fulltext.queryNodes&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The built-in algorithms saved time too: &lt;code&gt;key_people&lt;/code&gt; is &lt;code&gt;algo.pageRank&lt;/code&gt; plus &lt;code&gt;algo.betweenness&lt;/code&gt; over a derived &lt;code&gt;COLLABORATED_WITH&lt;/code&gt; graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;The parser depends on HERB's phrasing. Component names come from a regex, so some are generic ("API"), and issues without a recognisable component land in an &lt;code&gt;Unspecified&lt;/code&gt; bucket. Early in the build, an apostrophe-versus-quote regex bug produced 36 junk issue titles (starting with "s ", from "customer's"). It was fixed by requiring that quote marks do not touch letters. &lt;/p&gt;

&lt;h2&gt;
  
  
  Checking it against ground truth
&lt;/h2&gt;

&lt;p&gt;HERB ships its own "person" questions, and some have answers that are pure graph patterns. We scored Lore against those with &lt;code&gt;eval/herb_eval.py&lt;/code&gt;, 47 of 70 in total:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Who worked on the previous release", for document-authoring roles: 40 of 40. QA specialists score 0 of 10, because they do not author documents and we do not model their involvement.&lt;/li&gt;
&lt;li&gt;"Which engineer's fixes were most often reopened": 6 of 10.&lt;/li&gt;
&lt;li&gt;"Which engineer has the most active bugs": 1 of 10.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The misses trace to two gaps. The 70 resolution messages we could not link leave some issues looking open, which inflates the active-bug counts. And we do not parse reassignment messages, so some issues sit with the wrong engineer.&lt;/p&gt;

&lt;p&gt;Decisions exist only for TrendForce, KnowledgeForce and ContextForce, and &lt;code&gt;trace_decision&lt;/code&gt; says so when asked about anything else. Collaboration is inferred from shared meetings only. Successors are ranked by past fixes, not current workload.&lt;/p&gt;

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

&lt;p&gt;First, the linking gaps behind the evaluation misses: more of the 70 unmatched resolutions, and reassignment messages. Then incremental ingest: since every "current" is derived from append-only history, new messages only need to add events and re-run the derivations. After that, quote-checked decision extraction for all 30 products.&lt;/p&gt;

&lt;p&gt;We used Claude Code (Claude Opus 5.5) as a coding assistant during the event, and Claude did the offline decision extraction, which is why every extracted item has to pass the quote check.&lt;/p&gt;

&lt;p&gt;The code is at &lt;a href="https://github.com/Petsteb/lore-company-brain" rel="noopener noreferrer"&gt;github.com/Petsteb/lore-company-brain&lt;/a&gt;. Any MCP client can act as the agent: &lt;code&gt;claude mcp add --transport http lore http://localhost:8000/mcp&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>graphdatabase</category>
      <category>ai</category>
      <category>hackathon</category>
      <category>python</category>
    </item>
  </channel>
</rss>
