DEV Community

SRIVAISHNAVI G
SRIVAISHNAVI G

Posted on

We Gave Every Freelance Client Their Own Hindsight Memory Bank

Three weeks after a client told me "no animations, please," I asked my AI assistant for a homepage concept for that same client. It handed me a hero section with a parallax scroll and a fade-in on every card.

The model wasn't broken. It just had no idea who the client was. Every session started from zero, and I was the only place the context lived.

I built Memora to fix that, and Hindsight is the memory layer underneath it.

A freelancer is a human database

A freelancer with 5 to 20 active clients carries a lot of state in their head:

  • What the client said in a WhatsApp voice note on Tuesday
  • Which design got rejected, and why
  • The budget that was agreed before scope crept
  • Which invoice is still pending
  • That Rahul makes the decisions and Priya only forwards them

That information is scattered across Gmail, chat threads, meeting notes, PDFs, and invoices. A general-purpose chatbot can help you write, but it treats every conversation as independent. You paste the same background in each time, and you still forget half of it.

I didn't want "ChatGPT for freelancers." I wanted a persistent client-memory system: something that remembers what a client said, what worked, what failed, and why.

How Memora hangs together

The core loop is simple:

Interaction → Extract → Retain → Recall relevant history → Generate → Update memory
Enter fullscreen mode Exit fullscreen mode

On top of that loop, the app has a handful of screens:

  • Clients: each client has a profile with company, industry, and priority.
  • Projects: each project belongs to a client and tracks progress and deadlines.
  • Memory timeline: a running record of what I've learned about each client, where I can capture a detail as a typed memory (fact, preference, feedback, and so on), scoped to a project or kept general.
  • AI assistant ("Memo"): a chat where I pick a client first, so answers are grounded in that client's memories, requirements, and payment context. It can also import a WhatsApp or email thread.
  • Payments: milestone schedules in INR, with reminders.

The frontend is a web app and the backend is an API service that talks to an LLM and to Hindsight. I won't spend more time on the UI, because the interesting engineering problem is the memory layer.

The core decision: one memory bank per client

My first instinct was one big memory store, with a client_id on every record and a filter at query time.

I dropped that quickly. A filter is a promise you have to remember to apply on every query. If one code path forgets it, Client A's budget shows up in Client B's email draft. For a freelancer, that isn't just a bug. It's a confidentiality problem.

Instead, each client gets its own bank in Hindsight. Isolation is structural instead of depending on every query remembering a filter, and "forget this client" has an obvious meaning: one bank, one place.

This is also why the assistant screen starts with a client picker. Choosing the client chooses the bank.

The write path looks like this:

from hindsight_client import Hindsight

hindsight = Hindsight(base_url=HINDSIGHT_URL)

def remember_interaction(client_id: str, project: str, text: str, source: str):
    hindsight.retain(
        bank_id=f"client-{client_id}",
        content=f"[project: {project}] [source: {source}] {text}",
    )
Enter fullscreen mode Exit fullscreen mode

Every note, imported chat, and piece of feedback goes through that function. I include the project in the content so a website redesign doesn't get mixed up with an SEO campaign for the same client.

The read path runs before generation:

def recall_context(client_id: str, task: str):
    return hindsight.recall(
        bank_id=f"client-{client_id}",
        query=task,
    )
Enter fullscreen mode Exit fullscreen mode

When I ask for "a new homepage concept for ABC," I don't decide which memories matter. The task is the query, and the memory layer returns the history relevant to that task. The model gets the relevant history for the current job, not everything ever said.

Not everything belongs in memory

The Payments screen taught me a design rule: exact facts don't belong in fuzzy memory.

A milestone of ₹2,00,000 due on 1 Oct, marked paid, is a structured record. I want to sum it, sort it, and send a reminder from it. It should live in ordinary structured storage.

What belongs in Hindsight is the context around it: "this client pays late unless reminded on the 3rd," or "they questioned the final invoice because the scope changed." The assistant reads both, structured records for the numbers and recalled memory for the story.

Typed memories, scoped to projects

In the timeline, every memory I capture has a type and a project. The type matters because "the client rejected the first design" (feedback) and "the client's industry is SaaS" (fact) get used differently.

The project scope matters because one client can have several engagements. Without it, a preference from one project quietly leaks into another.

Before and after

Without memory, my request and the assistant's answer look like this:

Me: Create a homepage concept for ABC.
Assistant: Sure! What style are you going for, and who's the audience?

I've answered those questions before. I shouldn't have to answer them again.

With the client's bank behind it, the same request starts from what I've already told it:

Me: Create a homepage concept for ABC.
Memo: Based on this client's history: dark theme, minimal animation, launch by October 5, and the first design was rejected as too crowded. Here's a sparse dark layout with a single call to action above the fold.

The difference isn't that the model got smarter. It's that it starts with the context I'd otherwise re-type.

Lessons learned

1. Isolate memory structurally, not with filters. One bank per client removed a whole class of leaks. If a mistake would be costly, make it impossible rather than unlikely.

2. Design for the empty bank. This one came straight from testing. I asked the assistant for "the details about" a client whose bank was still empty. Instead of saying "I have nothing on file for this client yet," it fell back to generic assistant behavior and offered to help with calculations. That is the worst possible answer for a memory product. An empty recall result is a state your prompt and UI must handle explicitly, and the honest response is to say so.

3. Keep exact facts out of memory. Amounts, dates, and statuses belong in structured records. Use memory for context and judgment.

4. Type and scope your memories. A type and a project on every memory cost almost nothing to capture and make retrieval far less noisy.

5. Query with the task. Letting the request drive recall meant less hand-tuned retrieval logic and more relevant context.

What's still hard, and what's next

Two problems are open:

  • Contradictions. A client says "dark theme" in September and "let's try light" in November. The newer decision should win, but the system has to know that, and for now I'd rather surface the conflict than guess.
  • Extraction quality. Memory is only as good as what gets retained. Vague notes make vague memories, and better retrieval can't fully fix that.

Next on my list is separating what a client explicitly said from what the agent inferred, with a confirm-or-ignore step before an inference becomes a preference, and a "Why this recommendation?" view that lists the memories behind an answer. If I can't see why an assistant suggested something, I have to re-verify it myself, which defeats the point.

Why an agent memory layer

I could have stored notes in Postgres and searched them with embeddings, and for a simple app that's a fair choice. But deciding what to retain, retrieving the right history for open-ended tasks, and keeping that history useful as a client relationship changes is exactly the work a dedicated memory layer exists to do. Hindsight let me build the freelancer workflow instead of building memory infrastructure.

If you want the background, Vectorize has a good overview of what agent memory is, and the Hindsight documentation covers the retain and recall APIs used above.

The assistant doesn't just remember what a client said. It remembers what worked, what failed, and why. That's the difference between a chatbot and a colleague.

Top comments (0)