S.S
All posts
Soufiane Sejjari··9 min

Inside a LangGraph Memory Pipeline: From Message to Memory

A teardown of the extraction pipeline behind iRemember: why LangGraph, how a chat message becomes structured memory candidates, deduplication, async processing, and the schema decisions that make retrieval work.

Inside a LangGraph Memory Pipeline: From Message to Memory

Most memory tutorials show you a prompt: "extract facts from this conversation." That prompt is maybe 10% of a production pipeline. Here is the other 90%, as built for iRemember.

Why LangGraph

Memory extraction is not one call. It is a stateful workflow:

message_batch
  -> candidate_extraction   (LLM: facts, events, people, decisions)
  -> entity_resolution      (LLM + rules: who is "she"? merge aliases)
  -> dedupe_consolidate     (vector match vs existing memories)
  -> importance_scoring     (signals -> weight)
  -> write                  (only if it survived all of the above)

Each step has different failure modes and deserves its own evaluation. LangGraph gives us an explicit graph with typed state between nodes — so we unit-test consolidation separately from extraction, retry vector-store failures without re-calling the extractor, and branch (skip writes entirely for small talk).

The schema that makes retrieval work

Every stored memory carries:

  • content — one atomic fact, phrased self-contained ("Sara prefers voice notes over calls"), never pronouns
  • type — preference | event | fact | decision | relationship
  • entities — resolved references to people/places/projects
  • timestamps — when said, when valid from/to
  • importance — learned weight, not a vibe
  • embedding — pgvector, computed at write time

The discipline that matters most: atomic, self-contained content. An extractor allowed to store "they talked about the wedding" has stored nothing retrievable.

Async everything

Extraction runs in Celery workers after the reply streams out. The chat loop only pays for retrieval. Two consequences worth engineering for:

  1. Read-after-write gaps — a user says "remember that I'm allergic to peanuts," the assistant confirms, but extraction completes two seconds later. We pin explicit remember-X statements into a fast-lane write path.
  2. Idempotency — retries happen. Every extraction job carries a message-range key so re-runs don't double-store.

Consolidation over append-only

Append-only memory is how bloat starts. When a candidate matches an existing memory (entity overlap + similarity above threshold), the graph routes to a consolidation node: update, merge, or supersede-with-timestamp. Contradictions are expected input, not errors — people change jobs and cities.

What I'd tell past me

  • Write the consolidation logic before polishing extraction prompts; dedup quality dominates perceived accuracy.
  • Evaluate each node independently with golden sets. End-to-end numbers hide which stage regressed.
  • Small talk should never reach the extractor. A cheap classifier gate saved us ~40% of extraction calls.
#LLM#Memory#LangGraph#Architecture

FAQ

Why use LangGraph for a memory pipeline instead of plain LLM calls?

Memory extraction is a multi-step stateful workflow: extract candidates, resolve entities, deduplicate against existing memories, decide writes. LangGraph models this as an explicit graph with typed state — testable per node, instead of one fragile mega-prompt.

Does memory extraction add latency to chat responses?

It shouldn't. Extraction runs asynchronously after the reply is sent — in our case via Celery workers. The conversation loop only waits for retrieval, not for memory writes.

How do you prevent duplicate or contradictory memories?

Candidates are matched against existing memories on entity overlap plus embedding similarity above a threshold. Matches trigger consolidation (update/merge/supersede) rather than blind inserts.

Soufiane Sejjari

Software Engineer · AI Researcher