<?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: aarushi ranjan</title>
    <description>The latest articles on DEV Community by aarushi ranjan (@aarushi_ranjan_d78d68052d).</description>
    <link>https://dev.to/aarushi_ranjan_d78d68052d</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3876897%2Fa9bf5c19-fc43-4b92-bfd6-621a27aa082e.jpg</url>
      <title>DEV Community: aarushi ranjan</title>
      <link>https://dev.to/aarushi_ranjan_d78d68052d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aarushi_ranjan_d78d68052d"/>
    <language>en</language>
    <item>
      <title>I Built an 8-Step Invoice Processing Pipeline — Here's What Memory Actually Changes</title>
      <dc:creator>aarushi ranjan</dc:creator>
      <pubDate>Mon, 13 Apr 2026 14:54:39 +0000</pubDate>
      <link>https://dev.to/aarushi_ranjan_d78d68052d/i-built-an-8-step-invoice-processing-pipeline-heres-what-memory-actually-changes-ll2</link>
      <guid>https://dev.to/aarushi_ranjan_d78d68052d/i-built-an-8-step-invoice-processing-pipeline-heres-what-memory-actually-changes-ll2</guid>
      <description>&lt;p&gt;Most AI agents are stateless. They process a request, return a result, and forget everything. For most tasks that's fine. For accounts payable, it's a disaster.&lt;/p&gt;

&lt;p&gt;I spent two days building Finley, an invoice intelligence agent for SMBs, and the most interesting engineering decision wasn't the LLM prompt design or the extraction logic. It was what happened when I wired in &lt;a href="https://github.com/vectorize-io/hindsight" rel="noopener noreferrer"&gt;Hindsight&lt;/a&gt; as a persistent memory layer. The before and after is stark enough that I want to document exactly how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the System Does
&lt;/h2&gt;

&lt;p&gt;Finley processes invoices through an 8-step pipeline:&lt;/p&gt;

&lt;p&gt;Upload → LLM Extraction → Memory Retrieval → Analysis → Decision → Output → Feedback → Memory Update&lt;/p&gt;

&lt;p&gt;A user uploads a PDF invoice. The backend extracts structured fields using Claude, queries a vendor-scoped memory namespace for historical context, runs analysis, and returns a verdict: approve, flag, or reject. After the user acts on that verdict, their action gets written back to memory for next time.&lt;/p&gt;

&lt;p&gt;The stack is straightforward: Node.js/Express backend, React/Vite frontend, Claude via the Anthropic SDK, and &lt;a href="https://vectorize.io/what-is-agent-memory" rel="noopener noreferrer"&gt;Hindsight agent memory&lt;/a&gt; for persistence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Memory Architecture
&lt;/h2&gt;

&lt;p&gt;Every vendor gets its own namespace in Hindsight:&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
function vendorKey(name) {&lt;br&gt;
  return &lt;code&gt;vendor:${name.toLowerCase().replace(/\s+/g, "_")}&lt;/code&gt;;&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="nx"&gt;So&lt;/span&gt; &lt;span class="s2"&gt;`Prakash Office Supplies Pvt. Ltd.`&lt;/span&gt; &lt;span class="nx"&gt;becomes&lt;/span&gt; &lt;span class="s2"&gt;`vendor:prakash_office_supplies_pvt._ltd.`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;When&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="nx"&gt;arrives&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;we&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="nx"&gt;most&lt;/span&gt; &lt;span class="nx"&gt;relevant&lt;/span&gt; &lt;span class="nx"&gt;past&lt;/span&gt; &lt;span class="nx"&gt;interactions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
javascript&lt;br&gt;
export async function retrieveVendorMemory(vendorName, _currentInvoiceId) {&lt;br&gt;
  const key = vendorKey(vendorName);&lt;br&gt;
  const data = await hindsightFetch(&lt;br&gt;
    &lt;code&gt;/v1/memories/query?namespace=${encodeURIComponent(key)}&amp;amp;limit=20&lt;/code&gt;&lt;br&gt;
  );&lt;br&gt;
  if (data?.memories) return data.memories;&lt;br&gt;
  return localGet(key).slice().reverse();&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
After processing, the user's feedback — approved, rejected, or overridden — gets written back:

javascript
const payload = {
  namespace: key,
  content: JSON.stringify(entry),
  metadata: {
    vendorName,
    invoiceId: entry.invoiceId,
    date: entry.date,
    verdict: entry.agentDecision,
    userAction: entry.userAction,
  },
};
await hindsightFetch("/v1/memories", "POST", payload);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The metadata fields let Hindsight perform semantic retrieval — it doesn't just do exact key lookup, it finds contextually relevant memories. That matters when you're asking "has this vendor submitted duplicate invoices before?" against a set of unstructured memory entries.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Analyzer Is Where Memory Actually Earns Its Keep
&lt;/h2&gt;

&lt;p&gt;The real work happens in the analyzer. It receives both the extracted invoice fields and the retrieved memory array, and passes them to Claude together:&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
const analysis = await analyzeInvoice(extracted, memory);&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;analyzeInvoice&lt;/code&gt;, the memory gets serialized into the prompt. The LLM can then reason over patterns: "Invoice #INV-2025-0009 for ₹47,500 — I've seen this vendor submit an invoice with this exact amount and number before. That's a duplicate."&lt;/p&gt;

&lt;p&gt;Without memory, the analyzer produces generic checks: amount format, required fields, basic plausibility. With 9 prior interactions in context, it starts surfacing things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"This vendor consistently invoices on Net-30 despite a Net-45 contract"&lt;/li&gt;
&lt;li&gt;"Rounding errors of ₹0.50–₹2.00 are a documented pattern on this vendor's billing system"&lt;/li&gt;
&lt;li&gt;"3 duplicate invoices in 6 months — hold for review"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't hard-coded rules. They emerge from the LLM reasoning over memory the agent has accumulated through normal use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fallback Is Non-Negotiable
&lt;/h2&gt;

&lt;p&gt;I designed the memory layer to degrade gracefully. If the Hindsight API key isn't set, everything falls through to an in-process Map:&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
if (!HINDSIGHT_API_KEY) {&lt;br&gt;
  console.warn("[memory] HINDSIGHT_API_KEY not set — using local fallback");&lt;br&gt;
  return null;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The local store works for demos and development. You lose cross-session persistence, but the pipeline itself stays intact. This mattered during building — I could test the full 8-step flow without a live Hindsight connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Invoice #1 vs Invoice #10" Actually Looks Like
&lt;/h2&gt;

&lt;p&gt;The demo script for Finley has two acts for a reason.&lt;/p&gt;

&lt;p&gt;Act 1: Select a new vendor with no prior history. The agent approves with 0 memory recalls. The checks are generic. It has no basis for anything else.&lt;/p&gt;

&lt;p&gt;Act 2: Select Prakash Office Supplies on their 10th invoice. The agent retrieves 9 prior interactions, flags the duplicate, auto-corrects the payment terms to Net-45 (learned from previous corrections), and holds for review. Same pipeline, same code, completely different outcome.&lt;/p&gt;

&lt;p&gt;That's not a prompt trick. It's what accumulated &lt;a href="https://vectorize.io/what-is-agent-memory" rel="noopener noreferrer"&gt;agent memory&lt;/a&gt; looks like in practice.&lt;/p&gt;

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

&lt;p&gt;Store structured metadata alongside content. Hindsight accepts a &lt;code&gt;metadata&lt;/code&gt; object with each memory entry. Using it to index verdict, userAction, and date makes retrieval more precise. Don't skip it.&lt;/p&gt;

&lt;p&gt;Namespace granularity matters. We went vendor-scoped. You could also namespace by vendor+invoice-type or vendor+region. The right granularity depends on how you want memory to cluster.&lt;/p&gt;

&lt;p&gt;The fallback determines your dev experience. If the memory layer has no local substitute, your development loop gets slow. Build the fallback first.&lt;/p&gt;

&lt;p&gt;Feedback is how the agent learns. The initial seeded memories are for demos. The real value builds up through actual user corrections over time. Design the feedback loop early — it shapes the whole memory schema.&lt;/p&gt;

&lt;p&gt;The code for Finley is at &lt;a href="_https://finley-rho.vercel.app_"&gt;&lt;em&gt;finley-rho.vercel.app&lt;/em&gt;&lt;/a&gt;. The full memory integration uses &lt;a href="https://hindsight.vectorize.io/" rel="noopener noreferrer"&gt;Hindsight&lt;/a&gt; — worth reading their docs if you're building anything that needs an agent to remember context across sessions.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>ai</category>
      <category>agents</category>
      <category>ui</category>
    </item>
  </channel>
</rss>
