<?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: shivansh sinha</title>
    <description>The latest articles on DEV Community by shivansh sinha (@shivanshsource).</description>
    <link>https://dev.to/shivanshsource</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%2F4116167%2F469ad6b1-350f-4889-a0ee-6a29d7fb8023.jpg</url>
      <title>DEV Community: shivansh sinha</title>
      <link>https://dev.to/shivanshsource</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shivanshsource"/>
    <language>en</language>
    <item>
      <title>Building a Chargeback Defense Agent That Isn't Allowed to Make Things Up</title>
      <dc:creator>shivansh sinha</dc:creator>
      <pubDate>Sat, 07 Nov 2026 18:17:04 +0000</pubDate>
      <link>https://dev.to/shivanshsource/building-a-chargeback-defense-agent-that-isnt-allowed-to-make-things-up-3d1c</link>
      <guid>https://dev.to/shivanshsource/building-a-chargeback-defense-agent-that-isnt-allowed-to-make-things-up-3d1c</guid>
      <description>&lt;h2&gt;
  
  
  What Recoup actually does
&lt;/h2&gt;

&lt;p&gt;A seller connects their Stripe account. When a dispute comes in, Recoup pulls the order record, shipping and delivery data, customer messages, and the seller's own policies, and drafts a response. It also tells the seller whether fighting is worth it at all, based on evidence strength and that seller's own track record with disputes like this one.&lt;/p&gt;

&lt;p&gt;The part we spent the most time on isn't the drafting. It's making sure the agent can learn from experience without letting that experience override what's actually true about the dispute in front of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Splitting the system in two
&lt;/h2&gt;

&lt;p&gt;We separated Recoup into a core application layer and an agent intelligence layer, talking to each other through a fixed API contract.&lt;/p&gt;

&lt;p&gt;The core layer owns facts: Stripe webhook ingestion, the database, evidence collection, submission, and deadline tracking. The agent layer owns judgment: classification, evidence checklists, claim validation, decision scoring, response drafting, and memory.&lt;/p&gt;

&lt;p&gt;That split exists for one reason: we didn't want the part of the system that reasons about a dispute to also be the part that decides what counts as evidence. Those need different levels of trust.&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
class EvidenceCollector:&lt;br&gt;
    def collect(self, dispute: Dispute) -&amp;gt; list[Evidence]:&lt;br&gt;
        return [&lt;br&gt;
            *self.order_service.get_receipts(dispute.order_id),&lt;br&gt;
            *self.shipping_service.get_delivery_records(dispute.order_id),&lt;br&gt;
            *self.messages_service.get_customer_history(dispute.customer_id),&lt;br&gt;
        ]&lt;/p&gt;

&lt;p&gt;Evidence collected this way is marked verified. Anything a seller uploads or types themselves is stored separately, as a claim, until something verifiable backs it up. That distinction runs through the entire system.&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%2Fva62kbl8a173fv52akvt.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%2Fva62kbl8a173fv52akvt.png" alt=" " width="800" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every item on that checklist traces back to a real source — a shop API or a shipping API — with a timestamp. Nothing on this screen was typed in by a person.&lt;/p&gt;

&lt;h2&gt;
  
  
  Giving the agent memory that can be wrong about something small, without being wrong about everything
&lt;/h2&gt;

&lt;p&gt;Most "AI with memory" we'd seen stores conversation history and retrieves it by similarity. That tells you what was said before, not what actually worked.&lt;/p&gt;

&lt;p&gt;We used &lt;a href="https://github.com/vectorize-io/hindsight" rel="noopener noreferrer"&gt;Hindsight&lt;/a&gt;, an open-source memory layer built for agents, to store something closer to experience: past dispute cases, which evidence won and which didn't, seller-specific operating rules, and beliefs that update as outcomes come in.&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
hindsight.retain(&lt;br&gt;
    bank=f"seller-{seller.id}",&lt;br&gt;
    content={&lt;br&gt;
        "dispute_id": dispute.id,&lt;br&gt;
        "reason_code": "item_not_received",&lt;br&gt;
        "evidence_used": ["delivery_photo", "tracking_number"],&lt;br&gt;
        "outcome": "won",&lt;br&gt;
    },&lt;br&gt;
    tags=["reason:not_received", "courier:bluedart", "outcome:won"],&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Say a seller has won most of their "item not received" disputes when a delivery photo was attached. The next time a similar dispute comes in:&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
similar = hindsight.recall(&lt;br&gt;
    bank=f"seller-{seller.id}",&lt;br&gt;
    query="item not received, courier BlueDart",&lt;br&gt;
    tags=["reason:not_received"],&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;That result becomes a concrete recommendation, backed by a number, not a vibe. In practice it looks like this once a seller has enough history:&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%2Fs1ypy4ok8ah4g5ju23f1.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%2Fs1ypy4ok8ah4g5ju23f1.png" alt=" " width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"Delivery photo wins 'not received' disputes, 62% with it versus 21% without, confidence 99%, n=30." That n=30 matters as much as the number itself — we show the sample size next to every belief, because a pattern from three cases isn't a pattern, it's a guess. The same page surfaces risky couriers and repeat disputers, and lets a seller add standing rules ("never fight disputes under 20") that apply to every future draft, no matter what memory says.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not letting the model write checks it can't cash
&lt;/h2&gt;

&lt;p&gt;Once we had evidence and memory, the easy part would have been letting an LLM write a confident-sounding rebuttal letter. We didn't want confident. We wanted correct.&lt;/p&gt;

&lt;p&gt;Every claim in a generated response has to point at a specific evidence ID. That evidence has to belong to the dispute being handled and has to be verified. Unsupported claims get blocked before the letter ever reaches the seller. Once a seller approves a draft, we hash it, so what gets submitted is provably what they reviewed, not something regenerated after the fact.&lt;/p&gt;

&lt;p&gt;This is the thing we think gets skipped too often in applied agent work: explainability and evidence integrity aren't a UI feature you bolt on. They're a constraint on the generation pipeline itself, or they don't mean anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing the loop on outcomes
&lt;/h2&gt;

&lt;p&gt;A dispute resolves, weeks later, as won or lost. That outcome gets retained back into the seller's memory bank, and Hindsight's reflection step updates the relevant beliefs. That's the actual loop:&lt;/p&gt;

&lt;p&gt;dispute → evidence → decision → response → outcome → memory → better next decision&lt;/p&gt;

&lt;p&gt;Zoomed out across a seller's full history, that loop is what the analytics layer is actually showing:&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%2Fagxpm4c3x55gpk21wsue.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%2Fagxpm4c3x55gpk21wsue.png" alt=" " width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;None of these numbers come from the LLM guessing at a summary. They're computed directly from closed disputes, and every rate on the page states how many cases it's based on, for the same reason the Playbook does: a win rate means nothing without knowing the sample behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we learned
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Facts and claims need to be different types from the start.&lt;/em&gt; We didn't bolt this on later, if a seller's unverified note and a verified shipping record live in t``he same table with the same trust level, you will eventually generate a letter that cites something that isn't real.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Fast signals matter more than slow ones.&lt;/em&gt; Dispute outcomes take weeks. A seller editing a draft or overriding a recommendation is immediate, and it happens far more often. We learn from both, but we didn't want the system sitting idle for a month waiting on the slow signal.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Idempotency is not optional with financial webhooks.&lt;/em&gt; Stripe will redeliver events. If a duplicate webhook creates a duplicate memory write, your agent "learns" the same lesson twice and its confidence numbers lie to you.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A belief needs a minimum sample size before it's allowed to speak.&lt;/em&gt; One win doesn't make a pattern. We gate recommendations on recurrence, and we show confidence and sample size explicitly rather than letting a thin pattern sound as certain as a strong one.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Separating what reasons from what's true is the real architecture decision.&lt;/em&gt; Everything else, which framework, which database, which LLM, was replaceable. The boundary between the agent's judgment and the system's facts was not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this goes next
&lt;/h2&gt;

&lt;p&gt;We're working on deeper evaluation of how memory actually changes recommendation quality over time, rather than just showing that it changes, and on supporting more payment processors beyond Stripe. The harder, more interesting problem is still ahead of us: measuring, honestly, how much a seller's own history should be allowed to influence a decision versus how much is just noise from too few cases.&lt;/p&gt;

&lt;p&gt;If you're building agents that are supposed to improve with experience rather than with longer prompts, &lt;a href="https://vectorize.io/what-is-agent-memory" rel="noopener noreferrer"&gt;agent memory&lt;/a&gt; is worth taking seriously as its own architectural layer, not a feature you add to a vector store at the end.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Repository:&lt;/em&gt; &lt;a href="https://github.com/shivansh-source/recoup" rel="noopener noreferrer"&gt;https://github.com/shivansh-source/recoup&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Stack:&lt;/em&gt; Python, FastAPI, Next.js, PostgreSQL, Hindsight, Docker, Stripe``&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
      <category>startup</category>
    </item>
  </channel>
</rss>
