<?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: Saloni verma</title>
    <description>The latest articles on DEV Community by Saloni verma (@saloni_verma_4fa1616e7109).</description>
    <link>https://dev.to/saloni_verma_4fa1616e7109</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%2F3970184%2F0867ffb5-4287-478c-86ce-ab6ff2a8e942.png</url>
      <title>DEV Community: Saloni verma</title>
      <link>https://dev.to/saloni_verma_4fa1616e7109</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saloni_verma_4fa1616e7109"/>
    <language>en</language>
    <item>
      <title>Replacing a CRM's Memory With Hindsight: A FastAPI + React Build Log</title>
      <dc:creator>Saloni verma</dc:creator>
      <pubDate>Fri, 05 Jun 2026 17:59:26 +0000</pubDate>
      <link>https://dev.to/saloni_verma_4fa1616e7109/building-a-deal-intelligence-agent-with-fastapi-react-and-hindsight-4e3o</link>
      <guid>https://dev.to/saloni_verma_4fa1616e7109/building-a-deal-intelligence-agent-with-fastapi-react-and-hindsight-4e3o</guid>
      <description>&lt;p&gt;I've shipped three SaaS tools in the last four years. Each one had some version of the same problem: the database knew what happened, but nothing could reason about it. You could query "show me all deals in negotiation stage" but not "which deals are about to slip and why." The gap between stored data and actionable intelligence required a human to fill it.&lt;/p&gt;

&lt;p&gt;The Deal Intelligence Agent was my attempt to close that gap with a persistent AI memory layer. This is the build log — what the architecture looks like, where it got complicated, and what &lt;a href="https://github.com/vectorize-io/hindsight" rel="noopener noreferrer"&gt;Hindsight by Vectorize&lt;/a&gt; made possible that I couldn't have built myself in reasonable time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full stack
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Backend:&lt;/em&gt; FastAPI with async throughout. Python 3.11. Services are split: MemoryService owns the Hindsight integration, LLMService owns Groq completions and prompt construction, DealService owns business logic, AutopilotService and RoleplayService are higher-level features built on top of those primitives.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Frontend:&lt;/em&gt; React with Vite, Tailwind, Framer Motion. Pages: Dashboard (risk heatmap + pipeline forecast), Chat (deal-scoped agent), Deals (CRUD + detail view), Intelligence (competitor radar + pattern analysis), Analytics (revenue forecasting), Autopilot (autonomous scan console), Roleplay (simulation interface).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Inference:&lt;/em&gt; Groq running Llama 3.3 70B. Fast enough that streaming feels instant.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Memory:&lt;/em&gt; &lt;a href="https://hindsight.vectorize.io/" rel="noopener noreferrer"&gt;Hindsight's persistent memory layer&lt;/a&gt; scoped per deal, with a local defaultdict fallback for development.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Deploy:&lt;/em&gt; Docker Compose locally, Render for production. The backend and frontend are separate services; nginx proxies the React build.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Hindsight replaced
&lt;/h2&gt;

&lt;p&gt;Before Hindsight, I had a PostgreSQL table called deal_events with columns deal_id, event_type, content, created_at. Simple. It stored everything. And it was useless for the agent because I couldn't do semantic search against it without building a separate embedding pipeline, a vector store, an indexing job, a retrieval API.&lt;/p&gt;

&lt;p&gt;That's a non-trivial amount of infrastructure for what is, conceptually, a simple feature: "given this question, find the relevant history."&lt;/p&gt;

&lt;p&gt;Hindsight replaces all of it. The SDK gives me client.memory.store for writes and client.memory.search for semantic retrieval. The embedding, indexing, and similarity search are handled. I define the structure of what I store; Hindsight handles the retrieval machinery.&lt;/p&gt;

&lt;p&gt;python&lt;/p&gt;

&lt;h1&gt;
  
  
  Write
&lt;/h1&gt;

&lt;p&gt;result = await asyncio.to_thread(&lt;br&gt;
    self.client.memory.store,&lt;br&gt;
    user_id=deal_id,&lt;br&gt;
    text=f"[{entry_type.upper()}] Deal {deal_id}: {content}",&lt;br&gt;
    metadata={"deal_id": deal_id, "type": entry_type, "content": content}&lt;br&gt;
)&lt;/p&gt;

&lt;h1&gt;
  
  
  Read
&lt;/h1&gt;

&lt;p&gt;results = await asyncio.to_thread(&lt;br&gt;
    self.client.memory.search,&lt;br&gt;
    user_id=deal_id,&lt;br&gt;
    query=query,&lt;br&gt;
    limit=limit&lt;br&gt;
)&lt;br&gt;
return results.get("memories", [])&lt;/p&gt;

&lt;p&gt;That's the entire retrieval pipeline. What would have taken a week of infra work — Pinecone setup, embedding model selection, chunking strategy, index management — is a two-call SDK interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  The async threading issue that surprised me
&lt;/h2&gt;

&lt;p&gt;Hindsight's Python SDK is synchronous. FastAPI is async. Running a synchronous SDK call inside an async handler blocks the event loop — which in practice means every request queues behind every Hindsight call. Under any real load, this degrades to single-threaded throughput.&lt;/p&gt;

&lt;p&gt;The fix is asyncio.to_thread, which runs the synchronous call in a thread pool and returns an awaitable:&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
result = await asyncio.to_thread(&lt;br&gt;
    self.client.memory.store,&lt;br&gt;
    user_id=deal_id,&lt;br&gt;
    text=entry["embedding_text"],&lt;br&gt;
    metadata=entry_metadata&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;This is the same pattern you'd use for any blocking I/O (database calls with a synchronous driver, file operations, etc.) in an async context. It's not complicated, but it's easy to miss and the failure mode is subtle — the app "works" under low load and quietly breaks under real usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Autopilot background task pattern
&lt;/h2&gt;

&lt;p&gt;The Autopilot feature — autonomous cross-deal scan, objection resolution, playbook generation — runs as a FastAPI BackgroundTask:&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
@app.post("/api/autopilot/run")&lt;br&gt;
async def run_autopilot(background_tasks: BackgroundTasks):&lt;br&gt;
    if autopilot_svc.is_running():&lt;br&gt;
        return {"status": "already_running"}&lt;br&gt;
    background_tasks.add_task(autopilot_svc.run_autopilot_loop)&lt;br&gt;
    return {"status": "started"}&lt;/p&gt;

&lt;p&gt;The loop runs asynchronously without blocking the HTTP response. The frontend polls /api/autopilot/logs for live log updates, giving the impression of streaming execution without a WebSocket. The logs are stored in a module-level list with timestamps and structured levels (INFO, PROCESS, RECALL, MATCH, SUCCESS) that the frontend uses to color-code the console output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Revenue forecasting from typed memory
&lt;/h2&gt;

&lt;p&gt;One feature that came almost for free from the write-everything approach: the revenue forecast is calculated from typed memory entries rather than from a separate analytics model.&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
async def get_pipeline_forecast(self) -&amp;gt; Dict:&lt;br&gt;
    stage_weights = {&lt;br&gt;
        "prospecting": 0.1, "qualification": 0.2,&lt;br&gt;
        "proposal": 0.4, "negotiation": 0.7, "closing": 0.9&lt;br&gt;
    }&lt;br&gt;
    weighted_pipeline = sum(&lt;br&gt;
        deal["deal_value"] * stage_weights.get(deal["stage"], 0.1)&lt;br&gt;
        for deal in active_deals&lt;br&gt;
    )&lt;/p&gt;

&lt;p&gt;Stage weights applied to deal values give a probabilistic forecast. The deal values and stages are updated from memory events — stage changes, outcome signals — which means the forecast updates automatically as memory is written. No separate ETL. No scheduled job. The memory layer is the source of truth for analytics.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd build differently
&lt;/h2&gt;

&lt;p&gt;The one architectural decision I'd revisit: I used deal_id as Hindsight's user_id from day one, which is the right call for per-deal memory isolation. But the &lt;a href="https://vectorize.io/what-is-agent-memory" rel="noopener noreferrer"&gt;agent memory&lt;/a&gt; query path for cross-deal reasoning — used by the Autopilot — requires iterating across all deal stores manually rather than issuing a single cross-pipeline query. For a system with hundreds of deals, that iteration becomes expensive. I'd design a separate "pattern" pipeline in Hindsight for cross-deal memories from day one, and write closed-deal summaries there at outcome time.&lt;/p&gt;

&lt;p&gt;The rest I'd keep. The write-everything discipline, the typed embedding prefixes, the in-process fallback, the background task pattern — all of those decisions held up under real usage.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/chaitanya07-ai/deal-intelligence-agent" rel="noopener noreferrer"&gt;github.com/chaitanya07-ai/deal-intelligence-agent&lt;/a&gt; | Live: &lt;a href="https://deal-intelligence-agent-1.onrender.com/" rel="noopener noreferrer"&gt;deal-intelligence-agent-1.onrender.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agentmemory</category>
      <category>hindsight</category>
    </item>
  </channel>
</rss>
