<?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: Sandhya G K</title>
    <description>The latest articles on DEV Community by Sandhya G K (@sandhyagk).</description>
    <link>https://dev.to/sandhyagk</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%2F3938594%2F33c002a2-ec2d-4666-a42d-3abbabb292e7.png</url>
      <title>DEV Community: Sandhya G K</title>
      <link>https://dev.to/sandhyagk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sandhyagk"/>
    <language>en</language>
    <item>
      <title>CRMs store data. I built something that remembers context.</title>
      <dc:creator>Sandhya G K</dc:creator>
      <pubDate>Mon, 18 May 2026 18:56:33 +0000</pubDate>
      <link>https://dev.to/sandhyagk/crms-store-data-i-built-something-that-remembers-context-5101</link>
      <guid>https://dev.to/sandhyagk/crms-store-data-i-built-something-that-remembers-context-5101</guid>
      <description>&lt;p&gt;The first question I get when I tell people we have no database is: "What do you mean, no database?"&lt;/p&gt;

&lt;p&gt;We built SalesMemory — a persistent memory layer for sales reps — on a stack with no PostgreSQL, no SQLite, no Redis, nothing. Every prospect interaction is stored as a memory. Every pre-call brief is generated by recalling those memories and feeding them to an LLM. The only persistence layer is &lt;a href="https://github.com/vectorize-io/hindsight" rel="noopener noreferrer"&gt;Hindsight&lt;/a&gt;, a semantic memory system built by Vectorize.&lt;/p&gt;

&lt;p&gt;That decision shaped everything about how the product works. It's worth explaining why we made it, what we got from it, and what we gave up.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem we were solving
&lt;/h2&gt;

&lt;p&gt;Sales reps carry 30 to 50 active prospects at a time. Each one has history — objections raised, budget signals, competitor comparisons, things said three calls ago that still matter. That context lives in CRM notes nobody reads, or only in the rep's head.&lt;/p&gt;

&lt;p&gt;The result: reps re-introduce themselves on calls. They miss that the prospect said budget is frozen until Q3. They forget the deal almost died over onboarding speed. Every call without memory is a wasted call.&lt;/p&gt;

&lt;p&gt;What we built: before a call, the rep types a name and gets a structured brief pulled from every past interaction — key objections, a 2–3 sentence recommendation, deal health, last contact. After the call, they log 2–3 sentences. That note goes into permanent memory. The next brief is better because of it.&lt;/p&gt;

&lt;p&gt;The question was how to build the persistence layer for this.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why we didn't reach for a database
&lt;/h2&gt;

&lt;p&gt;The standard move would be: PostgreSQL table, one row per interaction, columns for prospect name, timestamp, outcome, notes. Maybe a separate table for deal scores. Add an ORM, write migrations, deploy.&lt;/p&gt;

&lt;p&gt;That approach works. But it has a core limitation: it stores structured fields. You can query "all interactions where outcome = 'Objection logged'" or "last contact date for Priya Sharma." What you can't query is "everything relevant to Priya's concerns about onboarding speed," because that requires understanding the semantic meaning of freeform notes, not matching field values.&lt;/p&gt;

&lt;p&gt;Sales notes are not structured. Reps write "she seemed unsure about the timeline" or "he pushed back on implementation scope" — not "objection_type: onboarding, sentiment: negative." Any system that forces structured input is a system reps won't use. We've all seen the CRM graveyard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hindsight.vectorize.io/" rel="noopener noreferrer"&gt;Hindsight documentation&lt;/a&gt; describes a retain/recall/reflect model where memories are stored with semantic embeddings and retrieved by meaning, not by field matching. That's exactly what we needed. The question became: can we build the entire product on this, with no other persistence?&lt;/p&gt;

&lt;p&gt;The answer was yes, with one tradeoff we'll get to.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the memory layer actually works
&lt;/h2&gt;

&lt;p&gt;Storing an interaction after a call is a single function call:&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_interaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prospect_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
Prospect: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prospect_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
Date: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="si"&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="s"&gt;
Summary: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;summary&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="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;pipeline_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PIPELINE_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;content&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;prospect&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prospect_name&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="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;timestamp&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;call_log&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;The content is freeform text. The metadata is structured. This combination is important: the metadata is what makes the memory timeline reliable (filter by prospect name, sort by timestamp). The freeform content is what makes retrieval semantic.&lt;/p&gt;

&lt;p&gt;Recalling everything before a call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;recall_prospect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prospect_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;results&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;pipeline_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PIPELINE_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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prospect interactions with &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prospect_name&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;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;top_k=10&lt;/code&gt; means we get the 10 most semantically relevant memories for that prospect. Not just the 10 most recent. If a rep logged a note three months ago about a budget freeze and the current call is about pricing, that memory surfaces. A SQL &lt;code&gt;ORDER BY timestamp DESC LIMIT 10&lt;/code&gt; would miss it.&lt;/p&gt;

&lt;p&gt;The recalled context goes directly into the LLM system prompt. No transformation, no schema mapping — the LLM reads raw memory and returns a structured JSON brief with objections, recommendations, deal health score, momentum, risk, and confidence level.&lt;/p&gt;




&lt;h2&gt;
  
  
  The deal health score is never stored
&lt;/h2&gt;

&lt;p&gt;This is the part that surprised people most when we showed it.&lt;/p&gt;

&lt;p&gt;The Deal Health Score — a 0 to 100 integer with a label (Cold / Warming up / Engaged / Hot / At risk) — is recomputed on every brief request. We never write it to disk. There's nothing to migrate if the scoring logic changes. There's no stale score sitting in a row from six weeks ago.&lt;/p&gt;

&lt;p&gt;The LLM scores a deal using specific rubric logic we define in the system prompt:&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;BRIEF_SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
You are a sales intelligence assistant. You read raw memory from past prospect
interactions and return a structured JSON pre-call brief for a sales rep.

Deal health scoring guide:
* 0-20: Cold. No engagement, no signals, or long silence.
* 21-40: Warming up. Early interest but objections unresolved.
* 41-60: Engaged. Active conversations, some positive signals.
* 61-80: Hot. Strong signals, near decision stage.
* 81-100: Closing. Verbal commitment or trial agreed.

Return ONLY valid JSON. No explanation. No markdown. No code fences.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deductions include unresolved objections, budget uncertainty, long gaps since last contact, and competitor mentions without resolution. Additions include pilot agreed, budget confirmed, and clear next steps established.&lt;/p&gt;

&lt;p&gt;Real output for Priya Sharma (VP Sales, Rentokil) after four logged interactions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Score: 70/100
Label: Engaged
Momentum: ↑ Improving
Risk: "Data migration concerns may stall the deal"
Recommended action: "Provide a detailed data migration plan and timeline"
Confidence: Medium
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the score is a function of all recalled memory right now, it automatically gets more accurate as memory accumulates. You don't have a recalculation job. You don't have stale scores. You don't have a schema that breaks when you add a new scoring dimension.&lt;/p&gt;




&lt;h2&gt;
  
  
  The weekly digest: one call, all prospects
&lt;/h2&gt;

&lt;p&gt;The weekly digest endpoint does something non-obvious. The rep clicks "Generate my week" and gets a prioritized action plan across all their prospects — bucketed into needs attention now (no response in 5+ days, stalled deal), follow up this week (active deal, next step needed), and on track (waiting on prospect).&lt;/p&gt;

&lt;p&gt;The entire thing runs in a single LLM call:&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;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;all_prospects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_all_prospects&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;prospect_contexts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;all_prospects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;recalled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recall_prospect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;recalled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;prospect_contexts&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;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&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="n"&gt;recalled&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_digest_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prospect_contexts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;groq_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llama-3.3-70b-versatile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&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;role&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;system&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DIGEST_SYSTEM_PROMPT&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;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.3&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;1200&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All prospect contexts go into one prompt. The LLM sees everyone simultaneously and can reason comparatively — "James hasn't responded in 7 days while Priya just agreed to a pilot, so James is the priority." Five separate calls and a merge step would be slower, more expensive, and less coherent because the LLM couldn't make that comparison.&lt;/p&gt;

&lt;p&gt;This works specifically because &lt;a href="https://vectorize.io/what-is-agent-memory" rel="noopener noreferrer"&gt;agent memory&lt;/a&gt; lets us recall all prospect contexts quickly and cheaply, then hand them off in a single structured prompt.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we gave up
&lt;/h2&gt;

&lt;p&gt;No database means no SQL aggregations. You can't run "count all deals by stage" or "total revenue weighted by deal health" or "average time between first contact and pilot agreement." If SalesMemory ever needs a reporting layer, we'd have to add one — probably by maintaining a lightweight metadata index in parallel with the memory layer.&lt;/p&gt;

&lt;p&gt;For the core use case — helping a rep prepare for a call and log what happened — this tradeoff is fine. The semantic recall is what matters, and SQL can't do that.&lt;/p&gt;

&lt;p&gt;We also can't do bulk updates. If we change what "outcome" means, we can't write a migration that updates every stored interaction. The memories are immutable once stored. This is a real constraint if the data model needs to evolve significantly.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Metadata is what keeps retrieval from becoming chaos.&lt;/strong&gt; Storing &lt;code&gt;{"prospect": name, "outcome": outcome, "timestamp": timestamp, "type": "call_log"}&lt;/code&gt; on every memory is what makes the timeline view work. Without it, you're running full semantic search on everything and hoping the right things surface. Structure the metadata even when the content is freeform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic recall is not keyword search.&lt;/strong&gt; "Prospect interactions with Priya Sharma" returns contextually relevant memories, not exact matches. "She pushed back on timing" and "onboarding timeline is a concern" both surface for the same query. This matters because real sales notes are inconsistent by nature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt is the actual engineering work.&lt;/strong&gt; Vague scoring instructions produce vague scores. The rubric in the system prompt — specific deduction and addition logic, five labeled score bands, explicit confidence levels — is what makes the deal health score match what a human sales manager would say. We spent more time on the scoring prompt than on any single piece of backend code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No CRM update required is the real value prop.&lt;/strong&gt; The system works with informal, unstructured notes because &lt;a href="https://vectorize.io/what-is-agent-memory" rel="noopener noreferrer"&gt;persistent memory across sessions&lt;/a&gt; stores semantic meaning. Reps don't change how they write. That's why they actually use it. Every system that required structured input got abandoned.&lt;/p&gt;




&lt;p&gt;The no-database architecture is not the right choice for every product. It was right for this one because the core value is semantic recall, not structured queries. The moment you need SQL aggregations, you need a database alongside the memory layer.&lt;/p&gt;

&lt;p&gt;But for a system where the rep logs "she seemed nervous about the migration timeline" and three calls later the brief correctly surfaces data migration as the top risk — that's semantic memory doing what SQL never could.&lt;/p&gt;

&lt;p&gt;That's the gap between storing data and remembering context.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>rag</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
