How I Built a Client Memory System That Remembers Everything I Always Forget
I'm a freelancer. I manage somewhere between 8 and 15 clients at any given time. For years, my "system" was a combination of scattered notes, email threads I couldn't find, and sheer hope that I'd remember what a client told me three weeks ago.
It didn't work. I'd show up to calls underprepared. I'd ask a client about a deadline they'd already told me. I'd forget a preference that cost me a revision cycle. The problem wasn't discipline — it was that there was no good tool built for someone like me: not a team, not a sales org, just one person managing real relationships with real stakes.
So I built ClientIQ — a solo client intelligence platform where every interaction you log is stored as structured AI memory, and you can ask anything about any client in plain English and get a specific, accurate answer drawn from your own history.
Here's how it actually works, and the one decision that made everything else fall into place.
What the System Does
ClientIQ is a Next.js 14 app backed by Supabase and powered by two AI agents running on Groq's qwen3-32b model. The core loop is simple:
- You type a raw note — "Called Akshit, he wants the enquiry form above the fold and is nervous about mobile load time"
- A logging agent extracts structured data: topic, preference, sentiment score, any deadlines
- That data gets written to two places: Supabase (structured rows) and Hindsight (semantic memory)
- Later, you ask the chat panel: "What does Akshit care most about on this project?"
- The chat agent pulls all Hindsight memories for that client, builds a context-rich prompt, and streams back a specific answer
The interface is split: left side is your interaction timeline and log input, right side is the AI chat panel. On desktop it's a 60/40 split. On mobile it collapses to tabs.
The Core Technical Story: Why Two Storage Layers
The most important architectural decision I made was using two separate storage systems for every interaction: Supabase for structured queries and Hindsight for semantic memory. At first it felt like over-engineering. It turned out to be the thing that made the product actually useful.
Here's the problem with a single-store approach. If I only used Supabase, querying client history means writing SQL or building filter UIs — neither of which maps to how you actually think before a client call. You don't think "show me interactions where topic = 'design preferences' and sentiment > 0.7." You think "what does this person care about?"
If I only used a vector store, I'd lose the ability to calculate structured signals — health scores, unresolved deadlines, interaction frequency — things that require counting rows and averaging numbers, not semantic similarity.
So I kept both, and they don't overlap in purpose:
// After extracting structure from the raw note:
await supabase.from('interactions').insert({
client_id,
user_id,
topic: extracted.topic,
preference: extracted.preference,
deadline: extracted.deadline,
sentiment_score: extracted.sentiment,
raw_note: note,
})
// Then write the same interaction to Hindsight for semantic recall:
await hindsight.retain({
content: note,
metadata: { user_id, client_id, topic: extracted.topic }
})
The Supabase row is for counting, sorting, and scoring. The Hindsight memory is for answering questions in natural language. Every write hits both.
The Logging Agent: Turning Noise Into Structure
The logging agent is where most of the real work happens. A user types a free-text note — no dropdowns, no required fields, just a textarea. The agent's job is to turn that into something the system can reason about.
const extractionPrompt = `
You are a client intelligence extraction agent.
Extract structured data from this client interaction note.
Note: "${rawNote}"
Return JSON with:
- topic: string (main subject)
- preference: string | null (any stated preference)
- deadline: string | null (ISO date if mentioned)
- sentiment: number (0-1, how positive the interaction felt)
- confidence: number (0-1, how confident you are in the extraction)
`
const result = await groq.chat.completions.create({
model: 'qwen3-32b',
messages: [{ role: 'user', content: extractionPrompt }],
response_format: { type: 'json_object' }
})
The confidence score was a late addition and it earned its place. When confidence drops below a threshold, the UI surfaces the extracted fields to the user for quick review before logging. That one decision eliminated the silent bad writes that were making early chat answers unreliable.
The Chat Agent: Memory-First Answers
The chat panel is where Hindsight agent memory earns its cost. When you ask a question about a client, the agent doesn't search a database — it recalls memories.
// Recall all memories for this user + client combination
const memories = await hindsight.recall({
query: userQuestion,
filter: { user_id, client_id },
limit: 20
})
// Serialize into context
const memoryContext = memories
.map(m => `[${m.metadata.topic}] ${m.content}`)
.join('\n')
const systemPrompt = `
You are a client intelligence assistant with access to all logged interactions for this client.
Client history:
${memoryContext}
Answer questions specifically, based only on what's in the history above.
If something isn't recorded, say so directly.
`
The critical rule here — and I'll put it in bold because I got burned violating it twice during development — every Hindsight write and every query must include both user_id and client_id, never one without the other. Miss one and you either leak memories across clients or get empty recall results with no error message. The system fails silently, which is worse than failing loudly.
Because Hindsight holds the persistent memory, chat history only lives in React component state. Refreshing the page doesn't lose context — it reloads it from Hindsight on the next question. This was a deliberate choice: I didn't want to build a message persistence layer. Hindsight is the persistence layer.
The Health Score: No LLM Required
One feature I'm glad I didn't over-engineer is the client health score. It's a 0–100 number that appears on every client card, and it's calculated entirely from Supabase data — no model involved.
Four signals feed into it:
- Interaction frequency in the last 30 days (35% weight)
- Average sentiment across the last 10 interactions (35%)
- Count of unresolved deadlines (20%)
- Days since last contact (10%)
Scores above 75 are green. 45–74 are amber. Below 45 are red. It recalculates after every logged note.
The health score is the thing that surfaces client relationships that are quietly drifting. A client you haven't logged in 3 weeks will start going amber whether or not you've noticed. That's the point — the system notices before you do.
What It Looks Like in Practice
New user signs up, and a seed client with 8 realistic interactions is automatically created. The intent is to let users immediately understand what the product actually does with real data rather than an empty state.
The "Add Client" flow takes about 30 seconds: name, project type, email, optional initial briefing note. Hit "Create Client Brain" and the Hindsight memory namespace for that client is initialized.
From there, the client detail page is the primary surface. You log a note, the AI extracts structure (visible as a toast confirmation), and the interaction appears in the timeline. The chat panel on the right is ready immediately.
A realistic sequence looks like this:
Log: "Spoke with Akshit — he wants the banquet hall photos front and center, not the form. Said the last agency buried the gallery."
Later ask: "What visual preferences has Akshit mentioned?"
Answer: "Akshit has specifically requested that banquet hall photos are prominently featured above the fold, not the enquiry form. He referenced a negative experience with a previous agency that buried the gallery section."
That answer comes from Hindsight. It's specific, attributed to the actual logged note, and available instantly — even if you logged that note two months ago.
Lessons Learned
1. Two storage layers is not over-engineering when they serve different query patterns. Relational for aggregation and scoring, semantic for natural language recall. The boundary is clean once you commit to it.
2. Silent failures in AI pipelines are worse than loud ones. The low-confidence extraction review UI added maybe two hours of work and saved significant debugging time. Surface uncertainty visibly, even when it's inconvenient.
3. Memory isolation requires explicit, redundant enforcement. Always tag memories with every relevant identifier. Don't rely on ambient context to provide isolation. I check for both user_id and client_id at every write and every query, not just one.
4. The seed data pattern is underrated. New users who land on an empty dashboard don't understand your product yet. Seed data that demonstrates the full loop — log, structure, recall — teaches the value proposition in 30 seconds. The alternative is explaining it in onboarding copy that nobody reads.
5. Health scores beat dashboards. A single number per client that incorporates recency, sentiment, and open items is more actionable than any chart. It answers the question "which client needs my attention today?" without requiring any analysis.
Where This Goes
The current implementation handles the core loop well. The next layer I want to add is proactive surfacing — something that flags when a client health score drops without any action from me, or when a deadline is approaching that I logged three weeks ago and haven't touched since.
Agent memory is what makes that possible. Because every interaction is stored semantically, I can query across all clients at once — "which clients have mentioned budget concerns in the last 60 days?" — without building explicit schema for every possible query pattern.
That's the thing I keep coming back to. The structured fields in Supabase are for the questions I knew I'd want to ask when I built the system. Hindsight is for the questions I haven't thought of yet.
For a solo freelancer, that's the whole game.
ClientIQ is built with Next.js 14, Supabase, Hindsight, and Groq. The Hindsight SDK handles all memory retention and recall operations.



Top comments (1)
client iq best model