<?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: LOKESHWARAN A</title>
    <description>The latest articles on DEV Community by LOKESHWARAN A (@lokeshwaran_a_19).</description>
    <link>https://dev.to/lokeshwaran_a_19</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%2F4231735%2F043a88ce-ad07-4c0e-b759-7fa5e5c4f39c.jpg</url>
      <title>DEV Community: LOKESHWARAN A</title>
      <link>https://dev.to/lokeshwaran_a_19</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lokeshwaran_a_19"/>
    <language>en</language>
    <item>
      <title>Hindsight Recalled 126 Memories. I Kept 15.</title>
      <dc:creator>LOKESHWARAN A</dc:creator>
      <pubDate>Sat, 07 Nov 2026 11:15:13 +0000</pubDate>
      <link>https://dev.to/lokeshwaran_a_19/hindsight-recalled-126-memories-i-kept-15-3ldn</link>
      <guid>https://dev.to/lokeshwaran_a_19/hindsight-recalled-126-memories-i-kept-15-3ldn</guid>
      <description>&lt;p&gt;The first time I asked &lt;a href="https://github.com/vectorize-io/hindsight" rel="noopener noreferrer"&gt;Hindsight&lt;/a&gt; what it knew about a Payment Service connection timeout, it handed back 126 memory records. Plenty of them were the same sentence wearing a different date.&lt;/p&gt;

&lt;p&gt;That is the problem this post is about: not whether an agent can remember an old outage, but what you do with everything it remembers before a model reasons over it. I built RECALLOPS to investigate production incidents. It recalls what Hindsight retained from earlier ones, filters that memory, and asks Groq to reason over what's left. Most of the interesting code turned out to live between the recall call and the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does, end to end
&lt;/h2&gt;

&lt;p&gt;RECALLOPS is a FastAPI backend with a React front end. You submit an incident (service, problem, symptoms, severity) and the backend runs one loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Incident form (React)
        │
        ▼
FastAPI ── recall ──► Hindsight memory bank
   ▲                        │
   └── candidate memories ──┘
        │
        ▼
dedupe → classify → rank → group by incident ID
        │
        ▼
Groq: analysis citing [HM-n] memory references
        │
        ▼
SQLite record ── retain ──► Hindsight (for the next incident)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The memory layer is &lt;a href="https://hindsight.vectorize.io/" rel="noopener noreferrer"&gt;Hindsight&lt;/a&gt;. On &lt;code&gt;retain&lt;/code&gt;, an LLM extracts facts, entities and temporal data. On &lt;code&gt;recall&lt;/code&gt;, it runs semantic, keyword, graph and temporal search in parallel and reranks the merge. &lt;code&gt;reflect&lt;/code&gt; reasons across memories instead of looking one up. That is what I want from &lt;a href="https://vectorize.io/what-is-agent-memory" rel="noopener noreferrer"&gt;agent memory&lt;/a&gt;, and I wrote none of it. Everything lives in one bank, &lt;code&gt;recallops&lt;/code&gt;. SQLite holds the rows the dashboard needs, and Groq runs &lt;code&gt;openai/gpt-oss-120b&lt;/code&gt;.&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%2Ff0h9g8eg02svc8t85t63.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%2Ff0h9g8eg02svc8t85t63.png" alt="RECALLOPS dashboard showing incident counts and recent incidents" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The dashboard: incident records come from SQLite, memory lives in Hindsight.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Retaining incidents like a postmortem
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;retain&lt;/code&gt; is one call, so the work is in what you hand it. I write each incident as a small postmortem: ID, service, symptoms, and separate fields for root cause, resolution and outcome.&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="c1"&gt;# condensed from routes/incidents.py
&lt;/span&gt;&lt;span class="n"&gt;incident_memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
NEW PRODUCTION INCIDENT
Incident ID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;incident&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incident_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
Service: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;incident&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
Problem: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;incident&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;problem&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
Symptoms: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;incident&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;symptoms&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

Confirmed Root Cause: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;incident&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root_cause&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Not yet identified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
Confirmed Resolution: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;incident&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resolution&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Not yet applied&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
Confirmed Outcome: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;incident&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pending&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

AI Investigation: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;analysis&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

IMPORTANT: The AI investigation contains hypotheses
and recommendations. These are not confirmed facts
unless verified by an engineer.
&lt;/span&gt;&lt;span class="sh"&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;retain&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;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;incident_memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The incident ID is the only join key I get across memories, so it is always in the text. The defaults say "Not yet identified" instead of leaving a blank, so a missing root cause reads as unknown, not absent. The last paragraph is the part I would change. I retain the model's own analysis, hedged with that warning, which means guidance my agent wrote comes back later as "memory" next to real incidents.&lt;/p&gt;

&lt;h2&gt;
  
  
  126 recalled, 15 kept
&lt;/h2&gt;

&lt;p&gt;Recall is one call, and what comes back is candidate evidence, not an answer. For one Payment Service query, the similarity endpoint reported 126 retrieved records. The cause is mundane: one retain becomes several extracted facts, and every analysis run retains the incident again. One outage turns into a dozen paraphrases, each ending in metadata like &lt;code&gt;| When: 2026-11-06 | Where: Payment Service&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before anything reaches Groq, &lt;code&gt;prepare_historical_memories&lt;/code&gt; strips that metadata and the dates, drops exact and near-duplicates, tags and scores each survivor, and keeps the top 15.&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="c1"&gt;# are_conservative_duplicates()
&lt;/span&gt;&lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;containment_score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.90&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jaccard_score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;sequence_score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# prepare_historical_memories()
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;memory_lower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;relevance_score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HISTORICAL_INCIDENT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;relevance_score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;incident_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;relevance_score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;selected_memories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processed_memories&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The category (&lt;code&gt;HISTORICAL_INCIDENT&lt;/code&gt;, &lt;code&gt;INVESTIGATION_UPDATE&lt;/code&gt; or &lt;code&gt;INVESTIGATION_GUIDANCE&lt;/code&gt;) comes from keyword lists. I kept the duplicate test conservative on purpose, because merging two records that only look alike can delete the one fact that differs.&lt;/p&gt;

&lt;p&gt;This filter only runs on the similarity path. &lt;code&gt;/analyze&lt;/code&gt; still hands raw recall to the model, which is why its memory panel looks like this:&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%2Fl1ys4wqt9567tad035ya.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%2Fl1ys4wqt9567tad035ya.png" alt="The analyze endpoint's raw historical memories, with near-duplicate paraphrases" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Raw recall from &lt;code&gt;/analyze&lt;/code&gt;, paraphrases included.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the model cite its sources
&lt;/h2&gt;

&lt;p&gt;Filtering does not make the surviving 15 trustworthy, so the prompt does the rest. Each memory gets a stable label (&lt;code&gt;HM-1&lt;/code&gt; to &lt;code&gt;HM-15&lt;/code&gt;), and records are grouped by incident ID with a regex on &lt;code&gt;INC-...&lt;/code&gt;. Anything without an ID is labelled unlinked evidence, and the model is told never to count unlinked records as separate incidents. The evidence rules in the prompt include these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Every historical factual claim must include its
   memory reference, such as [HM-1].
3. Never invent memory IDs or incident IDs.
4. Similar symptoms do not prove identical root causes.
7. A resolution is documented only when the memory
   explicitly states that it was applied.
9. If evidence is missing, write "Not documented".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temperature is 0.1, and the prompt forbids recommending automatic production changes, so a human approves every fix. The UI turns each &lt;code&gt;HM-n&lt;/code&gt; into an expandable evidence card, so an engineer can click any claim back to the memory behind it.&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%2F0mf1hdo73zew4q17jo00.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%2F0mf1hdo73zew4q17jo00.png" alt="Historical evidence cards, each HM reference expanding to the original Hindsight record" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Each &lt;code&gt;HM-n&lt;/code&gt; reference expands to the original Hindsight record.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reflect: finding the pattern, not the record
&lt;/h2&gt;

&lt;p&gt;Recall answers "what happened." &lt;code&gt;reflect&lt;/code&gt; answers the question I actually cared about: what keeps happening?&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="c1"&gt;# routes/incidents.py
&lt;/span&gt;&lt;span class="n"&gt;result&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;reflect&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wired it to a Generate AI Insights button. Asked about the Payment Service, it laid out the pool exhaustion on November 6, the recurrence on November 7, and the fact that both times the fix was raising &lt;code&gt;maxPoolSize&lt;/code&gt; and restarting. Its conclusion: those are temporary "quick-fixes" that mask the underlying root causes. That framing comes from reading across records instead of fetching one, and it is the observation I wanted from a memory layer in the first place.&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%2Fwgjl5rlim4szqdk87mdk.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%2Fwgjl5rlim4szqdk87mdk.png" alt="AI Memory page with Hindsight memory search and current incident records" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Memory search over the Hindsight bank, above the SQLite incident records.&lt;/em&gt;&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%2Fadz159xewce9lx81tu1c.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%2Fadz159xewce9lx81tu1c.png" alt="AI Memory Insights generated from historical incidents" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Generate AI Insights, backed by &lt;code&gt;reflect&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Before and after
&lt;/h2&gt;

&lt;p&gt;The test case: Payment Service, "Database Connection Timeout", payment failures, connection pool at its maximum.&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%2Fmyy5m3ijx3i4mw6xct6k.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%2Fmyy5m3ijx3i4mw6xct6k.png" alt="New incident form for the Payment Service connection timeout" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The incident form.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without memory&lt;/strong&gt;, &lt;code&gt;generate_incident_analysis&lt;/code&gt; gets the incident form and the fallback line &lt;code&gt;No relevant historical incidents found.&lt;/code&gt; There is nothing to compare against and nothing to cite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With memory&lt;/strong&gt;, the 15 records that reached Groq were sorted before it said a word. Three linked to one confirmed incident, INC-1794015715580, from earlier that day. Ten were Payment Service records with no incident ID. One (HM-14) was guidance rather than an incident, and one (HM-15) belonged to an unrelated Authentication Service incident. Without grouping, that reads as fifteen outages. With it, the report named one confirmed incident and called the rest unlinked evidence that "cannot be assumed to be the same incident."&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%2Fm8ep4hw5khlu02n6qieu.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%2Fm8ep4hw5khlu02n6qieu.png" alt="Similarity report: 126 records retrieved, 15 selected, grouped by incident ID" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The similarity report: 126 records retrieved, 15 used, grouped by incident ID.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The analysis that followed refused to invent a root cause: "Not yet identified." It gave numbered hypotheses instead, each tied to history. For pool exhaustion: "Historically the same symptom has been caused by genuine pool saturation." For a connection leak, it pointed at the November 4–6 deployments.&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%2Fs2ld1wbd70qu4caekcva.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%2Fs2ld1wbd70qu4caekcva.png" alt="Groq incident analysis with known facts, unknown root cause and hypotheses" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Groq analysis: known facts, an open root cause, numbered hypotheses.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell you before you build this
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Treat recall as candidates, not evidence.&lt;/strong&gt; Dedupe, rank and cap before the model sees anything. 126 records in a prompt is mostly repetition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put a stable ID in every memory.&lt;/strong&gt; It was the only join key I had. Ten of my 15 records were Payment Service evidence with no ID, so the model had no way to tell whether they described one outage or ten.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the model cite or abstain.&lt;/strong&gt; &lt;code&gt;[HM-n]&lt;/code&gt; references and "Not documented" are the cheapest hallucination check I found. A claim without a reference is easy to spot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do not feed your agent's guesses back as memory unlabelled.&lt;/strong&gt; I hedge them in the retained text, but I still classify memories with keyword lists after recall, which is brittle. &lt;code&gt;retain&lt;/code&gt; also accepts &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;metadata&lt;/code&gt;, and I only pass &lt;code&gt;content&lt;/code&gt;. Labelling AI output at write time is the first fix I would make.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;reflect&lt;/code&gt; for patterns and &lt;code&gt;recall&lt;/code&gt; for evidence.&lt;/strong&gt; They answer different questions, and only recall gave me something I could cite.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What is still rough
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No feedback loop.&lt;/strong&gt; I store outcomes, but nothing verifies that a recommended fix worked. The dashboard's Resolved counter sits at zero because "resolved" is a string match on the outcome field.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thin data.&lt;/strong&gt; My bank is small and almost entirely one service, I have not benchmarked recall quality, and the dedupe thresholds are hand-set, not measured.&lt;/li&gt;
&lt;/ul&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%2Fnstfooa2qhqm0dq17rfr.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%2Fnstfooa2qhqm0dq17rfr.png" alt="Analytics dashboard with severity distribution and incidents by service" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Analytics, computed from SQLite.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;The code is on &lt;a href="https://github.com/lokhi192007/RECALLOPS" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. If you are building on agent memory, start with the &lt;a href="https://github.com/vectorize-io/hindsight" rel="noopener noreferrer"&gt;Hindsight repository&lt;/a&gt; and the &lt;a href="https://hindsight.vectorize.io/" rel="noopener noreferrer"&gt;Hindsight docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The rule I keep coming back to: every incident should leave behind knowledge that makes the next investigation easier, and every recalled memory should have to earn its place in the prompt.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://code.in/" rel="noopener noreferrer"&gt;Code.in&lt;/a&gt; and the Vectorize team.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
      <category>python</category>
    </item>
  </channel>
</rss>
