<?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: Someswararao Tellakula</title>
    <description>The latest articles on DEV Community by Someswararao Tellakula (@someswararaotellakula).</description>
    <link>https://dev.to/someswararaotellakula</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%2F4150351%2F1f9febf2-d3d5-4fd9-8604-6c0394d78b0c.png</url>
      <title>DEV Community: Someswararao Tellakula</title>
      <link>https://dev.to/someswararaotellakula</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/someswararaotellakula"/>
    <language>en</language>
    <item>
      <title>I Gave My Accounts-Payable Agent Hindsight Memory. It Stopped Asking.</title>
      <dc:creator>Someswararao Tellakula</dc:creator>
      <pubDate>Sun, 08 Nov 2026 03:52:46 +0000</pubDate>
      <link>https://dev.to/someswararaotellakula/i-gave-my-accounts-payable-agent-hindsight-memory-it-stopped-asking-49lj</link>
      <guid>https://dev.to/someswararaotellakula/i-gave-my-accounts-payable-agent-hindsight-memory-it-stopped-asking-49lj</guid>
      <description>&lt;p&gt;Every month, an invoice from a steel distributor arrives priced 3% above the purchase order, and every month someone in accounts payable stops, opens the contract, finds clause 7.2 (monthly steel-index adjustment, capped at 5%), and approves it. The matching software flags it perfectly. It just has no idea that this exact question was answered last month, and the month before.&lt;/p&gt;

&lt;p&gt;That is the gap I built Ledgerwise for: an agent that sits after the 3-way match and resolves invoice exceptions the way &lt;em&gt;your&lt;/em&gt; desk resolved them last time — and knows when that history isn't enough.&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%2Fxzjuqnybq11trtordgnx.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%2Fxzjuqnybq11trtordgnx.png" alt="Ledgerwise reviewing an escalated invoice" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the system does
&lt;/h2&gt;

&lt;p&gt;An AP desk matches three documents: the purchase order (what we agreed to buy), the goods receipt (what actually arrived), and the invoice (what the vendor wants paid). When they disagree you get an exception: a price variance, a short shipment, a GST rate that doesn't match the contract, a surcharge line nobody ordered, a re-sent duplicate, or a vendor letter saying "we've changed our bank account."&lt;/p&gt;

&lt;p&gt;Detecting those is a solved problem, and in Ledgerwise it's deliberately boring: &lt;code&gt;matching.py&lt;/code&gt; is deterministic Python with no model and no memory. It finds exceptions and measures them — a percentage for price, rupees for a surcharge, units for a short shipment.&lt;/p&gt;

&lt;p&gt;The interesting part is deciding. That's &lt;code&gt;agent.py&lt;/code&gt;, and it runs one loop per exception:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Recall&lt;/strong&gt; how this vendor's same kind of exception was resolved before.&lt;/li&gt;
&lt;li&gt;Apply a conservative &lt;strong&gt;precedent policy&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-resolve&lt;/strong&gt; if human precedent is consistent; otherwise &lt;strong&gt;escalate&lt;/strong&gt; with the precedents attached.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retain&lt;/strong&gt; every human decision, including the reason they typed, so the next one is easier.&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 from Vectorize. I picked it over rolling my own vector store because AP history isn't a pile of documents to search — it's a set of decisions with actors, amounts and dates, and I wanted something that extracts and organises those facts rather than just embedding paragraphs.&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%2F3zkavdzpoa7hx4qg4b4o.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%2F3zkavdzpoa7hx4qg4b4o.png" alt="Where Hindsight sits" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The core story: memory is only useful if you know when not to trust it
&lt;/h2&gt;

&lt;p&gt;The obvious design is: recall similar past decisions, and if the top result says APPROVE, approve. Run that against the replay data and it looks great for a few weeks — until a logistics vendor sends a ₹6,800 fuel surcharge. Every earlier surcharge from that vendor was approved, but they were ₹1,200–₹2,400, and the agreed cap is ₹2,500. Similarity says "same vendor, same exception, approved." The money says otherwise.&lt;/p&gt;

&lt;p&gt;That case reshaped the whole design. The question stopped being "what does memory say?" and became "is memory &lt;em&gt;enough&lt;/em&gt; here?"&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing memories the policy can actually use
&lt;/h3&gt;

&lt;p&gt;Each human decision is retained as one string with two parts: a prose sentence that &lt;a href="https://hindsight.vectorize.io/" rel="noopener noreferrer"&gt;Hindsight's retain pipeline&lt;/a&gt; extracts facts and entities from, and a structured line my policy can parse back out.&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;retain_decision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decided_by&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;vendor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vendors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vendor_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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory&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="nf"&gt;precedent_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;magnitude&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;decided_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;context&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;accounts payable exception resolution — &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; vendor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;document_id&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&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;vendor_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;vendor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&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;exception&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&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;decision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decided_by&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;decided_by&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;document_id&lt;/code&gt; keeps a re-decision on the same invoice from becoming a duplicate memory, and &lt;code&gt;timestamp&lt;/code&gt; uses the invoice date rather than "now", so temporal questions work.&lt;/p&gt;

&lt;p&gt;The magnitude matters as much as the decision. "Approved a 2.8% variance" and "approved a 4.2% variance" are different precedents.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recalling with the source attached
&lt;/h3&gt;

&lt;p&gt;Before touching an exception, the agent asks a question in plain English:&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="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&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;How were &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="sh"&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; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; exceptions on invoices from &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
         &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; resolved, and why?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;resp&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;self&lt;/span&gt;&lt;span class="p"&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;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&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;world&lt;/span&gt;&lt;span class="sh"&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;experience&lt;/span&gt;&lt;span class="sh"&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;observation&lt;/span&gt;&lt;span class="sh"&gt;"&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;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;include_chunks&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_chunk_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;include_chunks=True&lt;/code&gt; was the detail that made this work. Recall returns the extracted facts, which are great for humans and for reflection, but the policy needs exact numbers. Asking for source chunks brings the structured precedent line back alongside each fact.&lt;/p&gt;

&lt;h3&gt;
  
  
  The policy
&lt;/h3&gt;

&lt;p&gt;With precedents in hand, the rules are short and intentionally conservative:&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;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;approved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;MIN_PRECEDENTS&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;magnitude&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;cap&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;floor&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;magnitude&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ExceptionDecision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APPROVE&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;approved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; human approvals up to &lt;/span&gt;&lt;span class="si"&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;approved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;; this is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;magnitude&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cited&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;At least &lt;strong&gt;two human precedents&lt;/strong&gt; before acting.&lt;/li&gt;
&lt;li&gt;For amounts: approve only &lt;strong&gt;inside the range humans approved&lt;/strong&gt; (plus 10% headroom) and below anything they rejected. Outside that range, escalate with a lean — "close to past approvals" or "far from anything approved".&lt;/li&gt;
&lt;li&gt;For categorical exceptions (short shipment, GST mismatch, duplicate): act only if the &lt;strong&gt;last three human decisions agree&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bank-account changes always go to a human.&lt;/strong&gt; No amount of history makes a changed remit-to account safe to pay automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The agent never learns from itself.&lt;/strong&gt; Its own decisions are retained for audit, but tagged and excluded from precedent. Without that, one bad call becomes two precedents by Thursday.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The LLM (Groq, &lt;code&gt;openai/gpt-oss-120b&lt;/code&gt;) writes the explanation and the vendor email. It never makes the pay/no-pay decision, and if it's down, a template takes over.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like over eight weeks
&lt;/h2&gt;

&lt;p&gt;I replay eight weeks of invoices from six vendors, each with one realistic recurring quirk, and play the AP lead whenever the agent escalates.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;W1&lt;/th&gt;
&lt;th&gt;W2&lt;/th&gt;
&lt;th&gt;W3&lt;/th&gt;
&lt;th&gt;W4&lt;/th&gt;
&lt;th&gt;W5&lt;/th&gt;
&lt;th&gt;W6&lt;/th&gt;
&lt;th&gt;W7&lt;/th&gt;
&lt;th&gt;W8&lt;/th&gt;
&lt;th&gt;Overall&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Without memory&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With Hindsight&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;60%&lt;/td&gt;
&lt;td&gt;60%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;67%&lt;/td&gt;
&lt;td&gt;56%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's the share of exception invoices resolved with no human, on the replay dataset. Across the run, zero of the automatic decisions were ones the AP lead would have overturned.&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%2F17aptewgzqkmcv1yku5p.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%2F17aptewgzqkmcv1yku5p.png" alt="Learning curve" width="300" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The dips are the part I'm proudest of:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 1, Kaveri invoices 18% GST on a 12% line.&lt;/strong&gt; No memory, so it comes to me. I reject and type: &lt;em&gt;"HSN 2839 is contracted at 12% GST; ask Kaveri for a credit note. Never pay the 18% version."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 3, same quirk.&lt;/strong&gt; Recall returns two human rejections. The agent rejects on its own, cites both invoice numbers, and drafts the credit-note email. Nobody touches it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 5, steel at +3.1%.&lt;/strong&gt; Humans had approved up to +2.8%. The agent escalates with "leaning approve" instead of extrapolating. I approve, and the learned range widens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 6, Meridian Electricals sends a "we've moved banks" letter.&lt;/strong&gt; Memory has six clean Meridian invoices. It escalates anyway.&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%2Fpbij3tek0834gdt2u3fl.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%2Fpbij3tek0834gdt2u3fl.png" alt="Bank-change guardrail" width="776" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The "Brief" button calls Hindsight's &lt;code&gt;reflect&lt;/code&gt; for a vendor and returns what a new joiner should know before the payment run: fuel surcharges up to ₹2,500 are pre-agreed, one at ₹6,800 was rejected. That's the institutional knowledge that usually leaves when the AP lead goes on leave.&lt;/p&gt;

&lt;h2&gt;
  
  
  See it run
&lt;/h2&gt;

&lt;p&gt;Youtube link: &lt;a href="https://youtu.be/3vHxH_v8v6" rel="noopener noreferrer"&gt;https://youtu.be/3vHxH_v8v6&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try the live demo: &lt;a href="https://ledgerwise-1ydb.onrender.com/" rel="noopener noreferrer"&gt;https://ledgerwise-1ydb.onrender.com/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. The reason field is the product.&lt;/strong&gt; I made the UI refuse to save a decision without a typed reason. Those sentences are what escalations show, what &lt;code&gt;reflect&lt;/code&gt; summarises, and what makes the memory auditable. "Approved" is a log line; "approved under clause 7.2, index sheet attached" is knowledge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Store the magnitude, not just the outcome.&lt;/strong&gt; Similarity search will happily match "same vendor, same exception" and ignore that this one is three times bigger. Every precedent carries its number, and the policy compares numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Decide what memory is allowed to override before you write any recall code.&lt;/strong&gt; Bank changes, unknown POs and anything outside a learned range are human-only by design. Writing those guardrails first made the rest of the policy much easier to reason about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Don't let the agent cite itself.&lt;/strong&gt; Retaining the agent's own decisions is useful for audit and reflection. Counting them as precedent is how a system drifts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Make memory configurable, not magical.&lt;/strong&gt; A skeptical, literal bank disposition (skepticism 5, literalism 4) fit an AP desk better than the defaults. One approval of a 3% variance should not become permission for "price variances" in general.&lt;/p&gt;

&lt;p&gt;The honest limitation: Ledgerwise is only as good as the reasons people type. A desk that clicks "approve" with "ok" gets an agent that can count but can't explain.&lt;/p&gt;

&lt;p&gt;If you want to try the memory layer yourself, the &lt;a href="https://hindsight.vectorize.io/" rel="noopener noreferrer"&gt;Hindsight docs&lt;/a&gt; get you to a working retain/recall loop quickly, and Vectorize's explainer on &lt;a href="https://vectorize.io/what-is-agent-memory" rel="noopener noreferrer"&gt;what agent memory is&lt;/a&gt; is a good read on why this is different from RAG. Ledgerwise's code is on GitHub: &lt;a href="https://github.com/SomeswararaoTellakula/ledgerwise" rel="noopener noreferrer"&gt;https://github.com/SomeswararaoTellakula/ledgerwise&lt;/a&gt;&lt;/p&gt;

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

</description>
      <category>agents</category>
      <category>ai</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Hindsight taught my on-call agent which fixes backfire</title>
      <dc:creator>Someswararao Tellakula</dc:creator>
      <pubDate>Tue, 29 Sep 2026 16:29:38 +0000</pubDate>
      <link>https://dev.to/someswararaotellakula/hindsight-taught-my-on-call-agent-which-fixes-backfire-2h7l</link>
      <guid>https://dev.to/someswararaotellakula/hindsight-taught-my-on-call-agent-which-fixes-backfire-2h7l</guid>
      <description>&lt;h1&gt;
  
  
  Hindsight taught my on-call agent which fixes backfire
&lt;/h1&gt;

&lt;p&gt;At 11:06 on a Monday, checkout starts failing. The alert says &lt;code&gt;HikariPool-1 - Connection is not available, request timed out after 30000ms&lt;/code&gt;. I gave that alert to &lt;code&gt;gpt-oss-120b&lt;/code&gt; with no history. Its first recommendation: raise the connection pool from 40 to 80 per pod and restart the pods.&lt;/p&gt;

&lt;p&gt;At this company, that is how you cause an outage. Five months earlier, a restart bought four minutes before the pool exhausted again, and scaling out blew through the database's 500-connection limit and took payments down with it. Twelve pods at 80 connections is 960. It's all in a postmortem that nobody reads at 11:06 with a SEV1 going off.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;NeverTwice&lt;/strong&gt;, an on-call copilot that reads the postmortems for you, using &lt;a href="https://github.com/vectorize-io/hindsight" rel="noopener noreferrer"&gt;Hindsight&lt;/a&gt; as its memory. This post is about the design decision that mattered most: remembering &lt;em&gt;what didn't work&lt;/em&gt; as carefully as what did — and then making memory enforce it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;NeverTwice triages alerts for BasketBolt, a fictional 10-minute grocery delivery company in Hyderabad. I wrote six months of history for it: 15 postmortems across checkout (Spring Boot + HikariCP), payments (UPI through two payment providers), Kafka consumers, Redis, OpenSearch, SMS OTPs and Kubernetes. Each has real-shaped logs, a timeline, a root cause, open action items and every remediation attempt with its outcome: &lt;em&gt;worked&lt;/em&gt;, &lt;em&gt;didn't work&lt;/em&gt;, &lt;em&gt;made it worse&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The stack is small: Python/Starlette, a vanilla JS UI, Groq's &lt;code&gt;gpt-oss-120b&lt;/code&gt;, and Hindsight for everything the agent knows. Hindsight extracts facts and entities from what you &lt;code&gt;retain&lt;/code&gt;, searches them four ways at once on &lt;code&gt;recall&lt;/code&gt; (semantic, keyword, entity graph, time), consolidates repeated facts into observations, and answers questions with &lt;code&gt;reflect&lt;/code&gt;. Loading the 63 memory items took about a minute of extraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 1: store outcomes as separate, tagged memories
&lt;/h2&gt;

&lt;p&gt;If a postmortem is retained as one document, the restart that failed and the fix that worked sit in the same blob, and "restart" is such a natural first step that a model can hand it straight back. So each incident is split by 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="n"&gt;items&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;# "At 11:15 IST the team tried: Rolling restart ... Outcome: DID NOT WORK. ..."
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;context&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;iid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; remediation — &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;OUTCOME_LABEL&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;svc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&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;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;document_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;iid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:remediation:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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;tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base_tags&lt;/span&gt; &lt;span class="o"&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;kind:remediation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;outcome:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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;metadata&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="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;iid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;service&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&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;remediation&lt;/span&gt;&lt;span class="sh"&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;outcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For every alert, NeverTwice runs four recalls in parallel — similar incidents, fixes that worked, fixes that failed or backfired, and on-call rules — and the outcome recalls are scoped by tag:&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="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&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;Recalling fixes that failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="nf"&gt;dict&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;remediation that failed or made things worse for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;symptom&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&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;low&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&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;outcome:failed&lt;/span&gt;&lt;span class="sh"&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;outcome:made_worse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;tags_match&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;any&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&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;world&lt;/span&gt;&lt;span class="sh"&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;experience&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results are numbered &lt;code&gt;M1..Mn&lt;/code&gt;, and the model must cite them or incident IDs as evidence.&lt;/p&gt;

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

&lt;p&gt;Same model, same prompt, same alert; the only difference is memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without memory&lt;/strong&gt;, the model recommended raising the HikariCP pool to 80, restarting the pods, and said it had "seen this before" in &lt;strong&gt;INC-2025&lt;/strong&gt; — an incident that doesn't exist. NeverTwice checks every cited ID against what recall actually returned, so that chip shows up red as unverified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With Hindsight&lt;/strong&gt;, it recalled 28 memories and cited four real incidents (INC-2104, INC-2291, INC-2317, INC-2282). Its first step was a verification query, not a mitigation. It said to cut the new reconcile workers back and terminate idle-in-transaction sessions, and under &lt;em&gt;Don't do this&lt;/em&gt; it listed scaling checkout to 20 replicas (INC-2104) and changing the database instance mid-incident (INC-2282).&lt;/p&gt;

&lt;p&gt;[Screenshot: the Compare view]&lt;/p&gt;

&lt;h2&gt;
  
  
  The dead end: memory that informs isn't enough
&lt;/h2&gt;

&lt;p&gt;Then I read the memory answer closely. Step 4 was: &lt;em&gt;"If latency remains high, perform a rolling restart of checkout-api pods."&lt;/em&gt; Memory had recalled that restarts relapsed in two incidents. The model even cited INC-2317 as support — but the fix there was a rollback, not a restart. Recall worked; the model still ignored it.&lt;/p&gt;

&lt;p&gt;Prompting harder helps a little. What fixed it was treating memory as a check, not just context. After the model answers, a &lt;strong&gt;backfire guard&lt;/strong&gt; looks at every risky step — restarts, scaling out, raising timeouts, database limit changes — and asks Hindsight directly whether that kind of fix failed before for the same service:&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="n"&gt;query&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; of &lt;/span&gt;&lt;span class="si"&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; did not work or made things worse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&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;worked&lt;/span&gt;&lt;span class="sh"&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;raw&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&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="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;moved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;backfire_guard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MemoryContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If failures outnumber successes, the step moves to &lt;em&gt;Don't do this&lt;/em&gt; with the proof and a "caught by guard" badge. The "outnumber" rule matters: in one incident a restart &lt;em&gt;was&lt;/em&gt; the right fix (a rotated secret is only read at startup), so the guard can't be a blanket ban.&lt;/p&gt;

&lt;p&gt;[Screenshot: the "caught by guard" card]&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 2: let it learn in front of you
&lt;/h2&gt;

&lt;p&gt;One alert is a failure BasketBolt has never seen: Telugu and Hindi searches return nothing because new OpenSearch nodes were built without the &lt;code&gt;analysis-icu&lt;/code&gt; plugin. NeverTwice has nothing relevant to recall and says so, with low confidence.&lt;/p&gt;

&lt;p&gt;The responder clicks &lt;strong&gt;Resolve &amp;amp; teach&lt;/strong&gt;, records what worked, what didn't, and whether NeverTwice's advice was right. That becomes new memories — including an experience fact about NeverTwice's own advice. When the same error hits a new node five days later, it recalls the incident it was just taught and gives the exact fix.&lt;/p&gt;

&lt;p&gt;Hindsight also maintains four &lt;strong&gt;mental models&lt;/strong&gt; that act as living runbooks — &lt;em&gt;Recurring failure patterns&lt;/em&gt;, &lt;em&gt;Fixes that backfired&lt;/em&gt;, &lt;em&gt;Open action items&lt;/em&gt;, &lt;em&gt;On-call rules&lt;/em&gt; — refreshed automatically after consolidation. Three &lt;strong&gt;directives&lt;/strong&gt; keep &lt;code&gt;reflect&lt;/code&gt; honest: cite incident IDs, flag destructive actions, separate what worked from what backfired.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring it
&lt;/h2&gt;

&lt;p&gt;I didn't want "it feels smarter", so each alert has a checklist: steps it must recommend, known-bad steps it must not, incidents it should cite. A script triages each alert three times with and without memory and grades every answer.&lt;/p&gt;

&lt;p&gt;On the payments alert, memory passed 3/3. Without memory, the model recommended raising the payment-provider timeout in 3/3 runs — the exact change that turned a degraded provider into an outage in INC-2150. On the checkout alert, the no-memory answer recommended a bigger connection pool 3/3 times.&lt;/p&gt;

&lt;p&gt;Across all four alerts, three runs each:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Passed the checklist&lt;/th&gt;
&lt;th&gt;Recommended a fix that already failed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;With Hindsight memory&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11 / 12&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 / 12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Without memory&lt;/td&gt;
&lt;td&gt;2 / 12&lt;/td&gt;
&lt;td&gt;10 / 12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Without memory, the model's instincts were consistent and wrong: bigger connection pools, longer timeouts, more replicas, reset the consumer offsets. It never once found the dead-letter-queue fix for the crash-looping Kafka consumers, because that fix only exists in this team's history.&lt;/p&gt;

&lt;p&gt;The one memory miss is the interesting one. On the DNS alert, it suggested scaling CoreDNS "if the DaemonSet is already present". Memory holds two answers to that: scaling CoreDNS fixed INC-2160 and failed in INC-2305, where the real cause was a missing node-local DNS cache. With one success and one failure, the guard correctly doesn't veto it; the model has to read &lt;em&gt;why&lt;/em&gt; each one happened. That is where I'd push next: remembering the conditions under which a fix works, not just its outcome.&lt;/p&gt;

&lt;p&gt;My first checklist was wrong, too: it failed two good answers because "scale the reconcile workers down to 4" matched my "no scaling" rule. Grading your grader is part of the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  What didn't work, and what I'd change
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool calling breaks in new ways.&lt;/strong&gt; On the first live run, &lt;code&gt;gpt-oss-120b&lt;/code&gt; called a tool named &lt;code&gt;json&lt;/code&gt; that didn't exist, and Groq rejected the call. The model's arguments were fine, just under the wrong name, so NeverTwice now salvages them from the failed generation instead of paying for another call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free-tier limits shaped the design.&lt;/strong&gt; Groq's free tier allows 8K tokens per minute on &lt;code&gt;gpt-oss-120b&lt;/code&gt;, and a side-by-side comparison makes two calls. Four small tag-scoped recalls plus runbook excerpts keep a prompt around 3K tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyword grading is crude.&lt;/strong&gt; It undercounts answers phrased differently; an LLM judge would be fairer. I kept keywords because they're deterministic and I can read exactly why an answer failed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The data is synthetic.&lt;/strong&gt; The next step is importing real postmortems with the same schema.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The code, data and scripts are on GitHub: &lt;a href="https://github.com/SomeswararaoTellakula/nevertwice" rel="noopener noreferrer"&gt;https://github.com/SomeswararaoTellakula/nevertwice&lt;/a&gt;. You need a Hindsight Cloud key (or a local Hindsight container) and a Groq key; &lt;code&gt;./start.sh&lt;/code&gt; sets everything up and starts the app.&lt;/p&gt;

&lt;p&gt;The most valuable thing it did wasn't finding the right fix. It was stopping the wrong one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt; &lt;a href="https://github.com/vectorize-io/hindsight" rel="noopener noreferrer"&gt;Hindsight on GitHub&lt;/a&gt; · &lt;a href="https://hindsight.vectorize.io/" rel="noopener noreferrer"&gt;Hindsight docs&lt;/a&gt; · &lt;a href="https://vectorize.io/what-is-agent-memory" rel="noopener noreferrer"&gt;What is agent memory?&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
