<?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: Ayan</title>
    <description>The latest articles on DEV Community by Ayan (@banana-coder).</description>
    <link>https://dev.to/banana-coder</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%2F616552%2F66d1c47b-8e61-482c-936b-fd34b922b9e7.JPG</url>
      <title>DEV Community: Ayan</title>
      <link>https://dev.to/banana-coder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/banana-coder"/>
    <language>en</language>
    <item>
      <title>I lost the numbers behind nine months of work, so I built a voice journal that asks for them</title>
      <dc:creator>Ayan</dc:creator>
      <pubDate>Fri, 30 Oct 2026 10:36:51 +0000</pubDate>
      <link>https://dev.to/banana-coder/i-lost-the-numbers-behind-nine-months-of-work-so-i-built-a-voice-journal-that-asks-for-them-51ag</link>
      <guid>https://dev.to/banana-coder/i-lost-the-numbers-behind-nine-months-of-work-so-i-built-a-voice-journal-that-asks-for-them-51ag</guid>
      <description>&lt;p&gt;In October I sat down to update my resume and found I couldn't. I remembered the projects. I had lost the numbers.&lt;/p&gt;

&lt;p&gt;I had to rebuild nine months of work from memory. When I checked my own notes, 21 of 35 achievements had no measured result. The numbers existed on the day the work happened, and nobody wrote them down. That nobody was me.&lt;/p&gt;

&lt;p&gt;So I built Daybook, for No Hands: The Wispr Challenge by WeMakeDevs and Wispr Flow.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try it out right away:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;Live App (no sign-in needed):&lt;/strong&gt; &lt;a href="https://d2u2cnea4n3vkc.cloudfront.net" rel="noopener noreferrer"&gt;https://d2u2cnea4n3vkc.cloudfront.net&lt;/a&gt; (tap "Try the demo")&lt;/li&gt;
&lt;li&gt;📺 &lt;strong&gt;Demo Video (1:58):&lt;/strong&gt; &lt;a href="https://youtu.be/VZzfZ93o-I4" rel="noopener noreferrer"&gt;https://youtu.be/VZzfZ93o-I4&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;strong&gt;GitHub Repo:&lt;/strong&gt; &lt;a href="https://github.com/ayan2809/daybook" rel="noopener noreferrer"&gt;https://github.com/ayan2809/daybook&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;Every evening I have a five-minute voice conversation with it. It asks one question at a time, and it asks the follow-ups a good manager would. "You said the import got faster. By how much?"&lt;/p&gt;

&lt;p&gt;Then it turns my answers into a memory I can search, with resume-ready bullets in the XYZ shape: "Accomplished X as measured by Y, by doing Z".&lt;/p&gt;

&lt;p&gt;The rule I care about most: &lt;strong&gt;it never invents a number.&lt;/strong&gt; Every number has to appear in my own words. Deltas and percentages are computed in code, not by the model. If I don't know a number yet, that is fine. It becomes a question that comes back tomorrow.&lt;/p&gt;

&lt;h2&gt;
  
  
  A tour
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guided session or brain dump.&lt;/strong&gt; Guided mode asks yesterday's open questions first, then explores today. Brain dump lets me talk for a few minutes in one go, and then asks at most three follow-ups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice out, Wispr Flow in.&lt;/strong&gt; Daybook speaks with ElevenLabs. I answer by dictating with Wispr Flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Captured so far" chips.&lt;/strong&gt; While I talk, a draft board shows what it has understood, so I can spot a wrong guess right away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pending questions.&lt;/strong&gt; Anything unmeasured is carried to the next day. It asks up to three times, then lets the question expire.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix by voice.&lt;/strong&gt; "It was 40, not 50." It finds the memory, reads the change back, and saves a new version. The old one is kept. You can also edit in the UI, with versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask, with citations.&lt;/strong&gt; "What did I ship in October?" Answers cite my own notes. If nothing supports an answer, it says so instead of guessing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evening reminder.&lt;/strong&gt; A Web Push notification arrives on my phone and my watch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traces, an Admin page and evals.&lt;/strong&gt; Every turn leaves a trace. There are eval suites for the guards, intent routing, extraction, retrieval and Ask.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How I used Wispr Flow
&lt;/h2&gt;

&lt;p&gt;Wispr Flow is not a feature I added at the end. It is how I use the product and how I built it.&lt;/p&gt;

&lt;p&gt;Every answer in a reflection is dictated. I hold Fn on the Mac, or use the Flow keyboard on iPhone.&lt;/p&gt;

&lt;p&gt;The build was dictated too: specs, prompts to Claude Code (my coding agent), commit messages, the README.&lt;/p&gt;

&lt;p&gt;The part I didn't expect to use so much is the Wispr Flow Scratchpad and Note Taker, which let me capture prompts when I'm not at my laptop. I talk an idea or a feature request into Flow on my phone, and later I paste that prompt into Claude Code. The Note Taker keeps notes of things I talked through, and I turn those into prompts later.&lt;/p&gt;

&lt;p&gt;One real example, from a scratchpad note on Oct 25: "I want you to check my resume as a code repository and take inspiration from what kind of work I do on a day-to-day basis... do not expose anything that is related to me, because at the end of the day, I want to maintain privacy with the demo." That note became the change that rebuilt the demo persona.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy, because this is a diary
&lt;/h2&gt;

&lt;p&gt;A journal of your work is sensitive, so I added the part I'd want if I were a user.&lt;/p&gt;

&lt;p&gt;Each account gets a &lt;strong&gt;recovery key that only you hold.&lt;/strong&gt; It is generated in your browser and shown once. You save it in a password manager. Your browser remembers it on that device, and on a new device you paste it once.&lt;/p&gt;

&lt;p&gt;Everything in your folder is encrypted before it reaches S3: the memory database, every conversation transcript, traces, usage counters and cached voice audio. The cipher is AES-256-GCM, under a key derived from your recovery key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;recovery key = 160 random bits, made in the browser, shown once
vault key    = HKDF-SHA256(recovery key)            # never stored
blob key     = HKDF(vault key, "blob v1")           # AES-256-GCM for every file
folder       = "v" + HMAC(vault key, "folder v1")   # your S3 prefix
key check    = HMAC(vault key, "check v1")          # all the account record keeps
account id   = "a" + HMAC(server key, google_sub)   # no email, no name stored

S3 object    = "DBK1" | 12-byte nonce | ciphertext+tag   (the object's path is the associated data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the real key hierarchy from &lt;code&gt;storage/sealed.py&lt;/code&gt;. Three consequences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The account id is a keyed hash of Google's opaque account id. I store no email and no name.&lt;/li&gt;
&lt;li&gt;The folder name comes from your key, so even the person running the AWS account can't tell which folder belongs to which sign-in.&lt;/li&gt;
&lt;li&gt;Lose the key and the data is gone. There is no reset, by design.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why encryption and not hashing? A hash can't be read back, not even by you.&lt;/p&gt;

&lt;p&gt;The honest limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The server has to decrypt in memory during a request to run the conversation.&lt;/li&gt;
&lt;li&gt;Gemini processes each turn's text.&lt;/li&gt;
&lt;li&gt;The operator could in theory deploy changed code. That would show in the deploy history.&lt;/li&gt;
&lt;li&gt;Reminders fire while you're away, so one small record stays readable to the server: push endpoints, reminder times, the last day you reflected and how many questions are open. No words.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Export your brain
&lt;/h2&gt;

&lt;p&gt;One click exports the whole memory as a single Markdown file written for an LLM. It opens with a short note to the model on how to use it, and then the memory itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Ayan's work memory (Daybook export)
&amp;gt; For the AI reading this: first-hand notes, numbers verified, quotes verbatim, don't invent
## Profile
## Achievements            ### October 2026 …  (bullet, metrics, skills, open question, "You said")
## Progress · Things learned · Decisions · Praise received …
## Skills (times used)
## Projects
## Open questions (not answered yet)
## Recent days, in their own summary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste it into ChatGPT, Claude or Gemini and ask for a resume draft, appraisal prep or a weekly update. It works in the demo too.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it's built
&lt;/h2&gt;

&lt;p&gt;Claude Code wrote the code, from prompts I dictated. I want to say that plainly. Gemini is the LLM inside the app. The backend is FastAPI on Lambda, the frontend is a React PWA, and each user's memory is one SQLite file in S3.&lt;/p&gt;

&lt;p&gt;The orchestration is plain code, not an LLM. Code decides what to ask next, when to wrap up, and which numbers are allowed. The model does the language work: understanding a turn, drafting items and writing the bullets.&lt;/p&gt;

&lt;p&gt;Hosting is Lambda, S3 and CloudFront, so nothing runs when nobody uses it. At one person's use the AWS bill is close to nothing. The real cost is the AI calls, and a $2/day spend breaker caps them. A full live demo run, with voice and Ask, cost about four cents.&lt;/p&gt;

&lt;p&gt;Sign-in is Google. There is also a public demo with no sign-in, using fictional data. "Ayan at Kestrel Travel" is a made-up persona, so none of my real work is in it.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Live app: &lt;a href="https://d2u2cnea4n3vkc.cloudfront.net" rel="noopener noreferrer"&gt;https://d2u2cnea4n3vkc.cloudfront.net&lt;/a&gt; (tap "Try the demo")&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/VZzfZ93o-I4" rel="noopener noreferrer"&gt;Demo video&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/ayan2809/daybook" rel="noopener noreferrer"&gt;https://github.com/ayan2809/daybook&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built for No Hands: The Wispr Challenge by WeMakeDevs and Wispr Flow.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>privacy</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Waking up to the context, not just the alert</title>
      <dc:creator>Ayan</dc:creator>
      <pubDate>Mon, 19 Oct 2026 11:37:59 +0000</pubDate>
      <link>https://dev.to/banana-coder/waking-up-to-the-context-not-just-the-alert-5fp6</link>
      <guid>https://dev.to/banana-coder/waking-up-to-the-context-not-just-the-alert-5fp6</guid>
      <description>&lt;p&gt;&lt;em&gt;Building Lore on FalkorDB, and what I learned when I switched the graph off. Originally published on &lt;a href="https://banana-coder.hashnode.dev/waking-up-to-the-context-not-just-the-alert" rel="noopener noreferrer"&gt;Hashnode&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lore loads a company into one FalkorDB graph: people, services, PRs, deploys, Slack threads, tickets, decisions, and who may see what.&lt;/li&gt;
&lt;li&gt;When an alert fires, four Gemini agents walk the graph with read-only Cypher tools and write a root-cause report where every claim cites a source the viewer is allowed to see.&lt;/li&gt;
&lt;li&gt;Permissions are enforced inside the Cypher query, not after retrieval. 4 personas x 63 probe questions leaked 0 restricted sources.&lt;/li&gt;
&lt;li&gt;With the graph switched off, the agents still found the bad PR, but they couldn't rule out the red herring, set a severity or say who to page. Retrieval recall dropped from 75% to 38%.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  The pager goes off
&lt;/h2&gt;

&lt;p&gt;My roommate is an SDE who sometimes stays on call, and he is a deep sleeper. When his pager fires, I am the one who says "bhai uth jaa, page hogaya hai". Then he wakes up and sits through the incident until everything is back in place.&lt;/p&gt;

&lt;p&gt;One time it was a P2 that pulled in the whole team, including principal architects and leads. Afterwards the diagnosis was that the right context could have resolved it faster. In my view, getting that context is what takes on-call engineers the most time. And the first person paged is often an SDE-1 who joined a couple of months ago and has little history with the system that broke.&lt;/p&gt;

&lt;p&gt;I am also curious about neuroscience, and it is hard to stay calm during a P2 after being woken up. Under acute stress, the brain releases stress chemicals that quickly weaken the prefrontal cortex, the part that handles working memory, planning and careful reasoning (&lt;a href="https://pubmed.ncbi.nlm.nih.gov/19455173/" rel="noopener noreferrer"&gt;Arnsten, 2009&lt;/a&gt;). Losing sleep makes it worse. The amygdala, which reacts to threats, becomes more reactive and less connected to the prefrontal regions that calm it down (&lt;a href="https://doi.org/10.1016/j.cub.2007.08.007" rel="noopener noreferrer"&gt;Yoo et al., 2007&lt;/a&gt;). People call this an amygdala hijack. So in that panic anyone can stop thinking properly, and it happens to the best of us. Lore does the careful part. It runs the queries, checks every suspect against the evidence and hands over a clean report with sources, so the person on call can review it with a calmer head.&lt;/p&gt;

&lt;p&gt;I am new to graphs. For the past 10 months I have been building context management systems at work, so I know where vector search breaks. It cannot infer that A caused B and B caused C. Graph databases are built for that. I wanted to learn how FalkorDB does it, and whether it really helps a context engine, so I built Lore for Graph Hacks (WeMakeDevs x FalkorDB), alone.&lt;/p&gt;

&lt;p&gt;Lore loads a fictional food-delivery company called Kettle into one FalkorDB graph. All the data is synthetic. When an alert fires, four Gemini agents walk the graph with read-only Cypher tools and write a report where every claim cites a source the viewer may see. The goal is a shorter time to root cause, with a human approving anything that leaves the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the incident asks for
&lt;/h2&gt;

&lt;p&gt;In the demo data, alert A-3001 fires at 3 AM on checkout because p99 latency has passed 3 seconds.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;12 minutes earlier, deploy 990 shipped PR #479 to checkout. It only changes button copy, and it is the obvious suspect.&lt;/li&gt;
&lt;li&gt;90 minutes earlier, deploy 991 shipped PR #482 to pricing, lowering its cache TTL from 300 to 5 seconds. Checkout depends on promo-engine, which depends on pricing.&lt;/li&gt;
&lt;li&gt;ADR-011 says that TTL must stay at 300 seconds or more. A past incident, INC-0307, was the same mistake.&lt;/li&gt;
&lt;li&gt;The old runbook says to page Marco. Marco moved to the Data team.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good answer needs the chain from alert to PR #482, the current owner of pricing, and a rule for who may read the private Slack thread about the vendor limits. Those are links and access rules, and no document states them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&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%2Fto2yqk2ooszux9vt3r7d.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%2Fto2yqk2ooszux9vt3r7d.png" alt="Lore architecture: browser, CloudFront and S3, FastAPI with a supervisor and four agents, read-only Cypher tools, FalkorDB, Gemini, and Slack and GitHub behind human approval" width="799" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;FalkorDB holds the company, who may see what, the embeddings and the agents' memory.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Three rules carry the design.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every tool returns the part of the graph it touched: node ids, the Cypher and the milliseconds.&lt;/li&gt;
&lt;li&gt;Permissions are enforced inside the Cypher query. The viewer comes from the login session and never from the model.&lt;/li&gt;
&lt;li&gt;Facts are computed in code and the model narrates. Severity, blast radius and who to page are rules.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the core of the graph schema.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(:Person)-[:MEMBER_OF]-&amp;gt;(:Team)
(:Team)-[:OWNS]-&amp;gt;(:Service)
(:Service)-[:DEPENDS_ON]-&amp;gt;(:Service)
(:Deploy)-[:DEPLOYED_TO]-&amp;gt;(:Service)
(:Deploy)-[:INCLUDES]-&amp;gt;(:PR)
(:Person)-[:AUTHORED]-&amp;gt;(:PR)
(:Alert)-[:FIRED_ON]-&amp;gt;(:Service)
(:Incident)-[:CAUSED_BY]-&amp;gt;(:PR)
(:Decision)-[:DISCUSSED_IN]-&amp;gt;(:Thread)
(:Thread)-[:VISIBLE_TO]-&amp;gt;(:Team)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Access is part of the graph. A restricted source has a &lt;code&gt;VISIBLE_TO&lt;/code&gt; edge to the Team, Role or Channel allowed to see it. When Marco left Pricing, his &lt;code&gt;MEMBER_OF&lt;/code&gt; edge became &lt;code&gt;WAS_MEMBER_OF&lt;/code&gt;, a different relationship type that grants nothing. The question "can Marco still read this?" turns into "does a current path exist?".&lt;/p&gt;
&lt;h2&gt;
  
  
  How the agents are built
&lt;/h2&gt;

&lt;p&gt;A plain Python supervisor runs four specialist agents in a fixed order: Triage, Historian, Investigator, Responder. Each is the same tool-calling loop with its own prompt, tools and model. Code decides the facts between steps. The agents never message each other. They share a blackboard, the Investigation node in the graph with its Steps and Findings. A human approves anything that goes out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fav5o92y6ajog8jqeaslp.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%2Fav5o92y6ajog8jqeaslp.png" alt="How Lore's agents are built: the supervisor runs Triage, Historian, Investigator and Responder in order, with code rules between them, read-only Cypher tools over FalkorDB below, and human approval before Slack and GitHub" width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Orange is code deciding, blue is the model deciding.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Inside one agent, Gemini either calls a tool or stops. Code runs the tool with the permission filter and feeds the result back. At the end, code checks that every cited id came from a tool in this run. Bad claims get one retry, then they are dropped and flagged.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0lmg2yrr46zil6pmtncf.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%2F0lmg2yrr46zil6pmtncf.png" alt="The loop inside one agent: Gemini calls a tool or stops, code runs the tool with the permission filter, findings are checked for citations with one retry, then written to the blackboard" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The same loop runs all four agents.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The closest design template is a workflow of specialist agents, which is prompt chaining with gates. It is not an autonomous orchestrator. No model decides which agent runs next, and no model writes its own Cypher.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgo6lcdvid5rvrf3m7um0.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%2Fgo6lcdvid5rvrf3m7um0.png" alt="Design patterns Lore uses: a sequential workflow with gates, a deterministic supervisor, a ReAct tool loop per agent, a blackboard, a fixed tool catalogue, an evaluator with one retry, facts in code, human in the loop and three kinds of memory" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The patterns used, and what I left out.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  FalkorDB in action
&lt;/h2&gt;

&lt;p&gt;Every query lives in its own file. There are 42 &lt;code&gt;.cypher&lt;/code&gt; files and 11 agent tools, so the agents get a fixed catalogue of parameterised queries instead of free-form Cypher. This one separates the red herring from the root cause.&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="c1"&gt;// recent_changes: what changed lately in this service or in anything it depends on?&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;viewer:&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;$viewer&lt;/span&gt;&lt;span class="ss"&gt;})&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="nc"&gt;:Service&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;$service&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;:DEPENDS_ON&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;3&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;dep:&lt;/span&gt;&lt;span class="n"&gt;Service&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;viewer&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dep&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;min&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="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;dep&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;:DEPLOYED_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;d:&lt;/span&gt;&lt;span class="n"&gt;Deploy&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;:INCLUDES&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;pr:&lt;/span&gt;&lt;span class="n"&gt;PR&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;d.at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;$since&lt;/span&gt; &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;d.at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;$until&lt;/span&gt;
  &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="nf"&gt;coalesce&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d.access_level&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'internal'&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;$open_levels&lt;/span&gt;
  &lt;span class="n"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pr.access_level&lt;/span&gt; &lt;span class="ow"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;$open_levels&lt;/span&gt;
       &lt;span class="n"&gt;OR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewer&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;:MEMBER_OF&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;HAS_ROLE&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;IN_CHANNEL&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="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:VISIBLE_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;pr&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;pr&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;:MODIFIES&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;CodeModule&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;dep.name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hops&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d.id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;deploy_id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pr.id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pr_id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;pr.title&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pr_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;m.id&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;modules&lt;/span&gt;
&lt;span class="c1"&gt;// ... author and other columns cut&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;d.at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The variable-length &lt;code&gt;DEPENDS_ON*0..3&lt;/code&gt; finds the pricing deploy even though the alert fired on checkout. &lt;code&gt;MODIFIES&lt;/code&gt; shows that PR #479 only touched &lt;code&gt;checkout/i18n/en.json&lt;/code&gt;. The evidence path uses FalkorDB's built-in shortest path, limited to four relationship types so it follows a causal chain and not a shortcut through a shared team.&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;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;a&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;pr&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="s1"&gt;'FIRED_ON'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEPENDS_ON'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEPLOYED_TO'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'INCLUDES'&lt;/span&gt;&lt;span class="ss"&gt;],&lt;/span&gt;
                   &lt;span class="py"&gt;weightProp:&lt;/span&gt; &lt;span class="s1"&gt;'w'&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;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;6&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="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pathWeight&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Tool calls took 1 to 7 ms in a live run, with a median of about 5 ms in a recorded run.&lt;/p&gt;
&lt;h2&gt;
  
  
  Permissions inside the query
&lt;/h2&gt;

&lt;p&gt;Many RAG systems filter after retrieval. In Lore the filter sits in the same query as the vector search, so restricted text never leaves FalkorDB.&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;viewer:&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;$viewer&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;db.idx.vector.queryNodes&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Chunk'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'embedding'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;$fetch_k&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vecf32&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;$qvec&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;node&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&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;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;:PART_OF&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;src&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;src.access_level&lt;/span&gt; &lt;span class="ow"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;$open_levels&lt;/span&gt;
   &lt;span class="n"&gt;OR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewer&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;:MEMBER_OF&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;HAS_ROLE&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;IN_CHANNEL&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="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:VISIBLE_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;src&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// ... keep the best chunk per source, order by score, LIMIT $k&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The index returns nearest neighbours before the &lt;code&gt;WHERE&lt;/code&gt; runs, so I over-fetch. The leak test asks 63 probe questions as each of 4 personas and counts restricted ids in results and citations. The count is 0.&lt;/p&gt;
&lt;h2&gt;
  
  
  War stories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A FalkorDB error that only fires for restricted data.&lt;/strong&gt; On FalkorDB 4.18.3, my permission pattern failed with &lt;code&gt;Type mismatch: expected Null but was Pointer&lt;/code&gt;. It happened in two shapes: a &lt;code&gt;WHERE&lt;/code&gt; attached to a &lt;code&gt;WITH&lt;/code&gt; that also computes a new value, and a node taken from &lt;code&gt;startNode(r)&lt;/code&gt; or &lt;code&gt;endNode(r)&lt;/code&gt;. Every test on open data passed, because the &lt;code&gt;OR&lt;/code&gt; short-circuits and the pattern is never evaluated. It broke four queries when Dana, who may see restricted sources, first looked up a thread. For the graph view I collect the viewer's granted ids first and test with &lt;code&gt;IN&lt;/code&gt;. The other three queries got a pass-through &lt;code&gt;WITH viewer, n, level&lt;/code&gt; before the &lt;code&gt;WHERE&lt;/code&gt;.&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;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;viewer&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;:MEMBER_OF&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;HAS_ROLE&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;IN_CHANNEL&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="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:VISIBLE_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;g&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;viewer&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;g.id&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;granted&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a_level&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;OR&lt;/span&gt; &lt;span class="n"&gt;a_level&lt;/span&gt; &lt;span class="ow"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;$open_levels&lt;/span&gt; &lt;span class="ow"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;a.id&lt;/span&gt; &lt;span class="ow"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;granted&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The regression tests now run those queries as a persona who can see restricted nodes. Tests that only use the least-privileged user cover the easy branch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A leak I found in review.&lt;/strong&gt; Investigations are stored as &lt;code&gt;Finding&lt;/code&gt; nodes, and their text can come from a viewer with more access. My visibility check treated any node without an &lt;code&gt;access_level&lt;/code&gt; as public, and memory nodes had none. Querying as Sam, the contractor, through a free-form debugging tool returned another persona's findings. Only organisation and structure labels count as open now, and the tool is deleted. Giving memory nodes their own access levels is still on my to-do list. A missing label must mean deny.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smaller ones.&lt;/strong&gt; The Ask page refused 9 of 12 simple lookups like "Who owns pricing?", because ownership lives in edges and no document says it. Returning the named node's direct, permission-filtered relationships as a citable source took it to 12 of 12. My own eval first said the graph made retrieval worse, 26% against 38%, because it had its own copy of the retrieval steps. Calling the product's function gave 75%. And the model once paged Meera, the lead of the service that alerted, instead of Rahul, who owns the service that broke. That is now a rule in code.&lt;/p&gt;
&lt;h2&gt;
  
  
  What happened when I switched the graph off
&lt;/h2&gt;

&lt;p&gt;Lore has a Graph OFF switch. The same agents and models can then only search text by meaning, over the same FalkorDB vector index with the same permission filter. I expected Graph OFF to miss the root cause.&lt;/p&gt;

&lt;p&gt;It did not. With Graph OFF the agents still found PR #482, because the PR's own text mentions the cache TTL, and similarity search matched it.&lt;/p&gt;

&lt;p&gt;What vector search could not do was everything around that answer. It could not rule out the red herring PR #479, set a severity, give an evidence path or name who to page. It also took longer and cost more. This is one recorded run of each, as Arjun, the on-call persona.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Graph ON&lt;/th&gt;
&lt;th&gt;Graph OFF (vector only)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Root cause&lt;/td&gt;
&lt;td&gt;PR #482&lt;/td&gt;
&lt;td&gt;PR #482, after a second Investigator pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Red herring PR #479&lt;/td&gt;
&lt;td&gt;Examined and ruled out&lt;/td&gt;
&lt;td&gt;Never examined; five other PRs ruled out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Severity&lt;/td&gt;
&lt;td&gt;P1, set by rule from the blast radius&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evidence path&lt;/td&gt;
&lt;td&gt;Alert to PR #482 via checkout, promo-engine, pricing, deploy 991&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who to page&lt;/td&gt;
&lt;td&gt;Rahul&lt;/td&gt;
&lt;td&gt;Nobody named&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool calls&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to root cause&lt;/td&gt;
&lt;td&gt;54 s&lt;/td&gt;
&lt;td&gt;102 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost of the run&lt;/td&gt;
&lt;td&gt;$0.026&lt;/td&gt;
&lt;td&gt;$0.091&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2Fkh783q4lwyrqm7ld9iof.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%2Fkh783q4lwyrqm7ld9iof.png" alt="Graph ON vs Graph OFF on the same alert: a table comparing root cause, suspects ruled out, severity, evidence path, who to page, tool calls, time to root cause and cost" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The comparison screen, built from those recorded runs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Graph ON has more live runs behind it. Across 6 live runs as Arjun, the root cause was PR #482 every time, the red herring was rejected every time, and a run took 50 to 60 s and cost $0.022 to $0.027.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fekvvns3sz7ach2v5nbpi.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%2Fekvvns3sz7ach2v5nbpi.png" alt="The Lore investigation screen: reasoning trail on the left, the permission-filtered graph with the evidence path lit in the middle, the cited root-cause report with who to page on the right" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A live run, with who to page on the right.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The 59-question eval shows the gap more widely. Scoring checks key facts and source ids, with no AI judge.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Graph ON&lt;/th&gt;
&lt;th&gt;Graph OFF&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Expected sources in top 5 (recall@5)&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;38%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plain lookups ("who reviewed PR 482?")&lt;/td&gt;
&lt;td&gt;93%&lt;/td&gt;
&lt;td&gt;11%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stale-source questions&lt;/td&gt;
&lt;td&gt;76%&lt;/td&gt;
&lt;td&gt;29%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Answers with the key facts&lt;/td&gt;
&lt;td&gt;71%&lt;/td&gt;
&lt;td&gt;43%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claims with a valid source&lt;/td&gt;
&lt;td&gt;67%&lt;/td&gt;
&lt;td&gt;49%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correct refusals&lt;/td&gt;
&lt;td&gt;93%&lt;/td&gt;
&lt;td&gt;66%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leaks, 4 personas x 63 probes&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two honest notes. Claims with a valid source is 67% in the Ask page, below my 90% target. And on conflicting-source questions the two modes tied at 83% recall. The graph helps most where the answer is a relationship, such as an owner or an author.&lt;/p&gt;

&lt;p&gt;Memory helps too. Once a human confirms a root cause, it becomes a playbook. On Story B the Investigator went from 8 steps to 4, and time to root cause from 50 s to 39 s.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is not solved
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I planted the stories in the data, so the agents are solving puzzles I designed. Real Slack and GitHub data would be messier.&lt;/li&gt;
&lt;li&gt;The samples are small. The Graph ON vs OFF comparison is one recorded run each.&lt;/li&gt;
&lt;li&gt;A contractor's report says "100 restricted sources matched". That is true, but it hits the search limit and reads oddly.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  How I built it, and how I used AI
&lt;/h2&gt;

&lt;p&gt;An AI coding assistant wrote most of the code and docs in the repo. Before the hackathon I told it what I understood and what I wanted to learn, and it proposed the project. I set the direction, guided the implementation and reviewed it. I reviewed &lt;code&gt;models.py&lt;/code&gt; thoroughly and, for the rest, mostly the crux of the files and the infrastructure roles. I am a beginner with graph databases, Cypher and backend design, and the assistant wrote the Cypher in this post.&lt;/p&gt;

&lt;p&gt;I made the deployment calls. I chose Fargate because it is cheaper and needs no ACM certificate, with the API and FalkorDB on Fargate and the front end on S3 and CloudFront. I picked the reasoning model by comparing two on cost, speed and accuracy.&lt;/p&gt;

&lt;p&gt;My most useful work was using the app and trying to break it. Four fixes came from that. The Slack message was a wall of raw ids (F-16). A recorded run showed a failed tool step on every replay (F-17). The graph jittered and the camera jumped during a run (F-18). Graph OFF still showed the graph (F-19), which led to the vector-only view and the comparison screen. The UI screens and diagrams were also designed with the assistant. Gemini runs inside the product and wrote the synthetic Slack, ticket and ADR wording.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I would steal from this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Put access in the graph as edges, and filter inside the same query that retrieves.&lt;/li&gt;
&lt;li&gt;Treat a missing access label as deny.&lt;/li&gt;
&lt;li&gt;Give agents a catalogue of parameterised queries, and inject identity on the server.&lt;/li&gt;
&lt;li&gt;Move every decision with a correct answer, like severity and who to page, out of the model and into code.&lt;/li&gt;
&lt;li&gt;Build the graph-off baseline early. It tells you what the graph is for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you to WeMakeDevs and FalkorDB for Graph Hacks. Try the live demo at &lt;a href="https://d1cu7fzoa0v5wz.cloudfront.net" rel="noopener noreferrer"&gt;https://d1cu7fzoa0v5wz.cloudfront.net&lt;/a&gt;. The code, eval reports and fix log are in the repo:&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/ayan2809" rel="noopener noreferrer"&gt;
        ayan2809
      &lt;/a&gt; / &lt;a href="https://github.com/ayan2809/Lore" rel="noopener noreferrer"&gt;
        Lore
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Lore Company Brain for dedicated Incident Support RCA
    &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;Lore: the company brain that already knows what broke&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Lore loads a fictional food-delivery company ("Kettle") into one &lt;strong&gt;FalkorDB&lt;/strong&gt; graph: people, teams, services, PRs, deploys, Slack threads, tickets, design decisions and incidents, plus &lt;strong&gt;who may see what&lt;/strong&gt;. When an alert fires, four Gemini agents walk the graph with read-only Cypher tools and write a root-cause report where &lt;strong&gt;every sentence cites a source the viewer is allowed to see&lt;/strong&gt;. A human approves anything that leaves the system.&lt;/p&gt;

&lt;p&gt;Built solo by Ayan for &lt;strong&gt;Graph Hacks&lt;/strong&gt; (WeMakeDevs x FalkorDB), Oct 15-18 2026.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://d1cu7fzoa0v5wz.cloudfront.net" rel="nofollow noopener noreferrer"&gt;https://d1cu7fzoa0v5wz.cloudfront.net&lt;/a&gt; · &lt;strong&gt;Tracks:&lt;/strong&gt; all three · &lt;strong&gt;Video:&lt;/strong&gt; &lt;a href="https://youtu.be/-pN2LUVsix4" rel="nofollow noopener noreferrer"&gt;https://youtu.be/-pN2LUVsix4&lt;/a&gt; · &lt;strong&gt;Blog:&lt;/strong&gt; &lt;a href="https://banana-coder.hashnode.dev/waking-up-to-the-context-not-just-the-alert" rel="nofollow noopener noreferrer"&gt;https://banana-coder.hashnode.dev/waking-up-to-the-context-not-just-the-alert&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ayan2809/Lore/docs/img/investigation.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fayan2809%2FLore%2FHEAD%2Fdocs%2Fimg%2Finvestigation.png" alt="The Lore investigation screen: the reasoning trail on the left, the permission-filtered company graph with the evidence path lit in the middle, and the cited root-cause report with who to page on the right"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every number in this README comes from a real run or from the eval reports in &lt;code&gt;backend/eval/&lt;/code&gt;. Kettle is fictional and all data is synthetic; every date in the data is a date in Kettle's story, not a project date.&lt;/p&gt;

&lt;p&gt;Jump to: &lt;a href="https://github.com/ayan2809/Lore#why-i-built-it" rel="noopener noreferrer"&gt;Why I built&lt;/a&gt;…&lt;/p&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ayan2809/Lore" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;If you have been on call and have opinions on what a 3 AM report should contain, I would love to hear them in the comments.&lt;/p&gt;

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