<?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: Pratheeksh S</title>
    <description>The latest articles on DEV Community by Pratheeksh S (@pratheeksh7s).</description>
    <link>https://dev.to/pratheeksh7s</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%2F4191063%2Fadd03e43-13ba-4da3-9dd9-fe2e327c51d4.png</url>
      <title>DEV Community: Pratheeksh S</title>
      <link>https://dev.to/pratheeksh7s</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pratheeksh7s"/>
    <language>en</language>
    <item>
      <title>I built an AI on-call agent that never forgets an outage</title>
      <dc:creator>Pratheeksh S</dc:creator>
      <pubDate>Sat, 07 Nov 2026 15:56:21 +0000</pubDate>
      <link>https://dev.to/pratheeksh7s/i-built-an-ai-on-call-agent-that-never-forgets-an-outage-233m</link>
      <guid>https://dev.to/pratheeksh7s/i-built-an-ai-on-call-agent-that-never-forgets-an-outage-233m</guid>
      <description>&lt;p&gt;It's 3 a.m. Your phone buzzes. The payments service is throwing &lt;code&gt;KeyError: 'amount'&lt;/code&gt; after a deploy, and customers can't pay.&lt;/p&gt;

&lt;p&gt;You open the logs, then Slack, then old postmortems. Forty minutes later you find it: a stale Redis cache key from the old version. Then someone says the words every on-call engineer hates: &lt;em&gt;"Oh yeah, we had this exact problem in March."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That sentence is the reason I built &lt;strong&gt;DejaVu&lt;/strong&gt;, an AI incident agent that remembers every outage your team has ever fixed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem: teams forget
&lt;/h2&gt;

&lt;p&gt;Most outages are not new. The same few failures come back again and again: cache bugs, database connections running out, bad config, slow queues. They just look a little different each time. A different service, a different error message, a different person on call.&lt;/p&gt;

&lt;p&gt;The fix usually exists somewhere. It's in an old Slack thread, a postmortem nobody reads, or the head of an engineer who is asleep or has left the company.&lt;/p&gt;

&lt;p&gt;General AI chatbots don't solve this. They have never seen &lt;em&gt;your&lt;/em&gt; systems. Ask one about your outage and it will give you a polite, generic guess. What on-call teams need is not a smarter guess. They need &lt;strong&gt;memory&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;DejaVu works in three simple steps: &lt;strong&gt;recall, reason, retain.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Recall.&lt;/strong&gt; A new error comes in. DejaVu searches its long-term memory for past incidents with the same failure pattern, even when the words are completely different.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reason.&lt;/strong&gt; An LLM (GPT-OSS 120B, running on Groq) reads only those recalled incidents and returns a root cause, a step-by-step fix, a time estimate, and the exact past incidents it used as proof. It is not allowed to cite anything memory didn't return.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retain.&lt;/strong&gt; When the engineer clicks &lt;strong&gt;Fix worked&lt;/strong&gt; or &lt;strong&gt;Fix failed&lt;/strong&gt;, that outcome is saved as a new memory. The next outage gets solved faster.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The memory layer is &lt;a href="https://github.com/vectorize-io/hindsight" rel="noopener noreferrer"&gt;Hindsight&lt;/a&gt;, an open-source agent memory system by Vectorize. If you're new to the idea, Vectorize has a good explainer on &lt;a href="https://vectorize.io/what-is-agent-memory" rel="noopener noreferrer"&gt;what agent memory is&lt;/a&gt; and why it's different from simply stuffing documents into a prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same AI, with and without memory
&lt;/h2&gt;

&lt;p&gt;The moment that convinced me this works is the side-by-side test in the app.&lt;/p&gt;

&lt;p&gt;I simulate the cache &lt;code&gt;KeyError&lt;/code&gt; outage and ask the &lt;strong&gt;same model&lt;/strong&gt; twice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Without memory:&lt;/strong&gt; 20% confidence. &lt;em&gt;"Likely a schema mismatch after deploy. Check the code and cache."&lt;/em&gt; A reasonable guess, but still a guess.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;With Hindsight memory:&lt;/strong&gt; &lt;strong&gt;98% confidence.&lt;/strong&gt; &lt;em&gt;"Redis cache invalidation bug after the v3.2 deploy, leaving stale v1 keys."&lt;/em&gt; It gives the exact commands to flush the keys and restart the pods, and it cites 5 past incidents where this fix worked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same model. Same prompt. The only difference is memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why keyword search isn't enough
&lt;/h2&gt;

&lt;p&gt;In my dataset of 20 incidents, the cache bug happens 6 times, on 3 different services, with 6 different error messages: &lt;code&gt;KeyError&lt;/code&gt;, &lt;code&gt;TypeError&lt;/code&gt; on a cart object, HTTP 500s, &lt;em&gt;"password reset tokens rejected as expired"&lt;/em&gt;, and &lt;em&gt;"promo codes ignored at checkout"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A keyword search for "KeyError" finds one or two of these. Hindsight's semantic recall finds all of them, because it matches on &lt;strong&gt;meaning&lt;/strong&gt;, not on words. That's what lets DejaVu say &lt;em&gt;"seen this 5 times, and this fix worked every time"&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Hindsight is used
&lt;/h2&gt;

&lt;p&gt;Hindsight is the core of the project, not an add-on. DejaVu uses four of its operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;create_bank&lt;/strong&gt; makes a memory bank called &lt;code&gt;dejavu-incidents&lt;/code&gt;, with a mission: remember each incident's error, root cause, exact fix and whether it worked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;retain&lt;/strong&gt; stores each incident as plain English, with its real timestamp, a stable &lt;code&gt;document_id&lt;/code&gt; (so re-runs update instead of duplicating), and metadata like the service name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;recall&lt;/strong&gt; finds similar past incidents for a new error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reflect&lt;/strong&gt; answers big-picture questions across &lt;em&gt;all&lt;/em&gt; incidents, like &lt;em&gt;"What keeps breaking?"&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the recall step. Hindsight returns facts, and I group them by incident and rank them by the reranker's relevance score, not by recency, so an old but identical outage still wins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;recall_similar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_incidents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;memory_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bank_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;BANK_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;error_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fact&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;incident_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;fact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;incident_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reranker&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reranker&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;final&lt;/span&gt;
        &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;incident_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;incident_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;incident_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;match&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;facts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]})&lt;/span&gt;
        &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;match&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;match&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;facts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;match&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="n"&gt;max_incidents&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://hindsight.vectorize.io" rel="noopener noreferrer"&gt;Hindsight docs&lt;/a&gt; cover retain, recall and reflect in detail. I ran the Hindsight server locally, with Groq as its LLM provider, so the whole stack is free to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Confidence you can trust
&lt;/h2&gt;

&lt;p&gt;I didn't want the AI to just &lt;em&gt;say&lt;/em&gt; it was 98% sure. So the confidence score comes from memory, not from the model's mood:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3 or more past incidents where the same fix worked: 90 to 98%&lt;/li&gt;
&lt;li&gt;2 proven matches: 75 to 89%&lt;/li&gt;
&lt;li&gt;1 proven match: 55 to 74%&lt;/li&gt;
&lt;li&gt;Nothing proven: 30% or less, and it says &lt;em&gt;"No similar incident in memory"&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Under the score, the app shows the reason in plain words, like &lt;em&gt;"5 of 5 matching past incidents were fixed this way."&lt;/em&gt; Engineers can check every claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  It learns while you use it
&lt;/h2&gt;

&lt;p&gt;The part I enjoy most is watching it learn. When I click &lt;strong&gt;Fix worked&lt;/strong&gt;, the incident is retained in Hindsight in the background. A new green dot appears on the live memory map, and the next time the same alert fires, that fresh incident is often the best match.&lt;/p&gt;

&lt;p&gt;The learning curve tells the story. Across the 20 incidents, the average time to fix fell from &lt;strong&gt;44 minutes&lt;/strong&gt; for the first 5 to &lt;strong&gt;6 minutes&lt;/strong&gt; for the last 5. That's &lt;strong&gt;87% faster&lt;/strong&gt; as the memory grew.&lt;/p&gt;

&lt;p&gt;There's also a &lt;strong&gt;Lessons from memory&lt;/strong&gt; panel. A team lead can ask &lt;em&gt;"What keeps breaking?"&lt;/em&gt; or &lt;em&gt;"Which fixes failed, and why?"&lt;/em&gt;, and Hindsight's reflect reasons over every incident to answer, with incident numbers as proof.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memory beats a bigger model.&lt;/strong&gt; The jump from 20% to 98% came from memory, not from a smarter LLM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rank by relevance, not recency.&lt;/strong&gt; My first version preferred recent incidents, and it missed old outages that were identical. The reranker score fixed that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ground every claim.&lt;/strong&gt; Forcing the model to cite only recalled incidents, and dropping any it made up, made the answers far more trustworthy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan for free-tier limits.&lt;/strong&gt; Groq's free tier allows about 8,000 tokens a minute, so the loader paces itself and the app has a fallback when reflect is busy.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;DejaVu is open source and runs on a laptop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/Pratheeksh-7S/dejavu-incident-agent" rel="noopener noreferrer"&gt;https://github.com/Pratheeksh-7S/dejavu-incident-agent&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;3-minute demo: &lt;a href="https://youtu.be/Nu_sktguGMk" rel="noopener noreferrer"&gt;https://youtu.be/Nu_sktguGMk&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hindsight: &lt;a href="https://github.com/vectorize-io/hindsight" rel="noopener noreferrer"&gt;https://github.com/vectorize-io/hindsight&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On-call will always be stressful. But it shouldn't mean solving the same outage twice. With memory, every outage you fix makes the next one easier.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Every outage, remembered.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>devops</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Recall Desk: three AI support agents that share one memory, and the memory is a graph.</title>
      <dc:creator>Pratheeksh S</dc:creator>
      <pubDate>Sat, 17 Oct 2026 19:21:51 +0000</pubDate>
      <link>https://dev.to/pratheeksh7s/recall-desk-three-ai-support-agents-that-share-one-memory-and-the-memory-is-a-graph-11j1</link>
      <guid>https://dev.to/pratheeksh7s/recall-desk-three-ai-support-agents-that-share-one-memory-and-the-memory-is-a-graph-11j1</guid>
      <description>&lt;p&gt;Title&lt;br&gt;
Recall Desk: three AI support agents that share one memory, and the memory is a graph.&lt;br&gt;
Published&lt;br&gt;
true&lt;br&gt;
Description&lt;br&gt;
How I used FalkorDB as the memory and coordination layer for a multi-agent support desk (Graph Hacks, Track 02).&lt;br&gt;
Tags&lt;br&gt;
Falkordb, AI, GraphDatabase, Hackathon&lt;br&gt;
Cover image&lt;br&gt;
&lt;a href="https://github.com/Pratheeksh-7S/Recall-Desk-/raw/main/docs/graph-path.png" rel="noopener noreferrer"&gt;https://github.com/Pratheeksh-7S/Recall-Desk-/raw/main/docs/graph-path.png&lt;/a&gt;&lt;br&gt;
Every support chat I've been in has the same weak spot. You explain your problem to the first person, they transfer you, and you explain it again. Most AI agent demos copy this behaviour. Each agent has its own prompt and its own chat history, and "memory" is a long text log passed from one agent to the next.&lt;/p&gt;

&lt;p&gt;For the WeMakeDevs × FalkorDB Graph Hacks hackathon (Track 02: Agent Memory and Coordination), I built Recall Desk, which tries something different. Three agents (triage, billing and technical) answer customers together, and they never hand context to each other in prompts. Everything an agent learns goes into a FalkorDB graph, and every other agent reads it from there, in the same session or a later one.&lt;/p&gt;

&lt;p&gt;Live demo: &lt;a href="https://recall-desk.onrender.com" rel="noopener noreferrer"&gt;https://recall-desk.onrender.com&lt;/a&gt; (free tier, so the first load can take a minute)&lt;br&gt;
Code: &lt;a href="https://github.com/Pratheeksh-7S/Recall-Desk-" rel="noopener noreferrer"&gt;https://github.com/Pratheeksh-7S/Recall-Desk-&lt;/a&gt;&lt;br&gt;
Video: &lt;a href="https://github.com/Pratheeksh-7S/Recall-Desk-/raw/main/docs/demo.mp4" rel="noopener noreferrer"&gt;https://github.com/Pratheeksh-7S/Recall-Desk-/raw/main/docs/demo.mp4&lt;/a&gt;&lt;br&gt;
The story the demo tells&lt;br&gt;
Session 1. Priya, on the Team plan in eu-west, writes, "I was charged twice this month."&lt;/p&gt;

&lt;p&gt;Triage reads her memory from the graph and opens a ticket.&lt;br&gt;
The graph says (billing)-[:CAN_HANDLE]-&amp;gt;(billing topic), so triage hands the ticket off. The handoff is a node, not a message.&lt;br&gt;
The billing agent finds two paid $30 invoices for the same month, refunds the duplicate (a graph write), and stores a promise memory.&lt;br&gt;
She then adds, "Bulk Export has been stuck for hours. " The technical agent follows a 7-hop path from her plan to an open incident in her region, finds the other affected customers and the other tickets with the same root cause, and links her ticket CAUSED_BY the incident.&lt;/p&gt;

&lt;p&gt;Session 2. Priya comes back the next day. Before she types anything, triage says, "Welcome back." Last time: Billing issued a $30 refund for INV-5003…" Nobody asks her to explain again.&lt;/p&gt;

&lt;p&gt;Every answer in the UI comes with the graph path behind it, so you can see exactly why the agent said what it said.&lt;/p&gt;

&lt;p&gt;Why does the graph have to be there?&lt;br&gt;
My rule for the hackathon was simple: if the app still works without FalkorDB, the graph is only decoration. Recall Desk has no SQL tables, no vector store and no chat history kept in Python. Remove FalkorDB, and nothing works.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Root cause with variable-length paths
"Bulk export is stuck" is a question about connected data. The feature depends on services, which depend on other services, and somewhere down that chain there may be an incident in the customer's region:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;MATCH (c:Customer {id:$customer_id})-[:IN_REGION]-&amp;gt;(r:Region)&lt;br&gt;
MATCH p=(c)-[:SUBSCRIBES_TO]-&amp;gt;(:Plan)-[:INCLUDES]-&amp;gt;(f:Feature)&lt;br&gt;
        -[:DEPENDS_ON*1..6]-&amp;gt;(s:Service)&amp;lt;-[:ON]-(i:Incident {status:'open'})-[:IN_REGION]-&amp;gt;(r)&lt;br&gt;
WHERE toLower(f.name) = toLower($feature)&lt;br&gt;
RETURN f.name AS feature, s.name AS root_service, i.id AS incident, length(p) AS hops, p&lt;br&gt;
ORDER BY hops LIMIT 3&lt;br&gt;
In SQL this would be a recursive CTE and several joins. In Cypher it's one pattern, and the path p it returns is what the UI draws.&lt;/p&gt;

&lt;p&gt;The same idea answers "Is it just me?" (walk back from the incident to every customer whose plan reaches the broken service) and "Who else has this?" (tickets that share a transitive dependency). algo.pageRank('Service','DEPENDS_ON') shows which services matter most.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Memory as nodes, not text
There are three kinds of memory, all in the graph:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Episodic: (:Session)-[:HAS_TURN]-&amp;gt;(:Turn)-[:NEXT]-&amp;gt;(:Turn), with FOLLOWED_BY linking one session to the next.&lt;br&gt;
Semantic: (:Customer)-[:HAS_MEMORY]-&amp;gt;(:Memory), plus LEARNED_IN, WRITTEN_BY and ABOUT edges, so every fact knows where it came from and which agent wrote it.&lt;br&gt;
Procedural: (:Procedure {successes, failures}). When a customer marks a ticket "not resolved", the playbook's failure count goes up. The agent ranks playbooks by a smoothed success rate, so after enough failures it switches to a better one by itself:&lt;br&gt;
MATCH (p:Procedure)-[:FOR_TOPIC]-&amp;gt;(:Topic {name:$topic})&lt;br&gt;
WITH p, (p.successes + 1.0) / (p.successes + p.failures + 2.0) AS rate&lt;br&gt;
RETURN p.name AS name, round(rate * 100) AS success_rate&lt;br&gt;
ORDER BY rate DESC LIMIT 1&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Coordination with graph writes
When triage passes a ticket to billing, it creates a Handoff node with FROM, TO and FOR edges, plus a CARRIES edge to each of the customer's memories. The specialist reads those memories instead of asking the customer again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two agents may try to claim the same ticket at the same moment. A single atomic statement settles it:&lt;/p&gt;

&lt;p&gt;MATCH (t:Ticket {id:$tid})&lt;br&gt;
SET t.owner = coalesce(t.owner, $agent)&lt;br&gt;
WITH t MATCH (a:Agent {name:t.owner})&lt;br&gt;
MERGE (t)-[:ASSIGNED_TO]-&amp;gt;(a)&lt;br&gt;
RETURN t.owner AS owner&lt;br&gt;
I tested this with 30 agents racing for one ticket: one owner and one ASSIGNED_TO edge every time. Memory writes use MERGE, so a load test of 16 threads × 200 operations (about 390 ops/s) produced zero duplicate memories.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One graph per company
Each tenant gets its own graph (recalldesk:acme, recalldesk:globex). I seeded the same customer email into both companies and checked that nothing leaks across.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keeping the LLM honest&lt;br&gt;
The LLM (Claude, GPT or Gemini, set by an environment variable, with a rules-only offline mode) does two jobs: it works out what the customer wants, and it words the reply. It never writes Cypher. It picks from a fixed catalogue of parameterised tools, and the customer id is always forced to the real customer.&lt;/p&gt;

&lt;p&gt;Every reply is path-checked. If the LLM mentions an invoice, incident, article or ticket id that isn't in the graph results or the recalled memory, the reply is thrown away and an answer built straight from the graph rows is sent instead. The facts come from the graph, and the LLM only does the writing.&lt;/p&gt;

&lt;p&gt;What I learned&lt;br&gt;
Graphs make memory explainable. A memory node with a LEARNED_IN edge to the session and a WRITTEN_BY edge to the agent answers "Why do you think that?" for free.&lt;br&gt;
Coordination is a data problem. Routing (CAN_HANDLE), handoffs and ticket ownership are rows and edges, not prompt engineering. That makes them testable.&lt;br&gt;
Variable-length paths are the killer feature for support. Real root causes sit several hops away from the symptom.&lt;br&gt;
Stack&lt;br&gt;
FalkorDB (Docker locally, FalkorDB Cloud in production), Python + FastAPI, vanilla JS with vis-network for the graph view, and GitHub Actions running the end-to-end tests against a real FalkorDB. It's deployed on Render.&lt;/p&gt;

&lt;p&gt;I built this with help from an AI coding assistant (Claude Code), as disclosed in the README.&lt;/p&gt;

&lt;p&gt;If you try the live demo, pick Priya Raman and type "I was charged twice". Then open the Memory graph tab and watch the agents write to it.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>database</category>
      <category>devchallenge</category>
    </item>
  </channel>
</rss>
