# Per-contact memory for production conversational AI agents — research report

Researched 2026-07-24 for channels-manager (WhatsApp/IG/FB bot, Dubai real estate). 5 research passes, 6-8+ searches each, primary sources fetched where reachable. Single-source claims flagged inline.

## Executive summary

Every mature system is hybrid — none uses one memory strategy alone. The pattern with the most rigor (MemGPT/Letta, Claude's memory tool, and independently converged on by LangGraph/Mem0/Zep): a **small always-in-context tier of discrete structured facts** + an **external, timestamped, on-demand store** for the rest — never one ever-growing blob, never pure vector RAG. Rewrite-vs-append: rolling-summary **rewrite** is fine only as a small, disposable "current state" cache; durable facts should be **discrete, timestamped, superseded** (not deleted, not re-summarized) — repeated LLM re-summarization measurably degrades quality (one paper found it can perform *worse than no memory*). Extraction should run per-turn but **asynchronously off the response path**, with a salience threshold and attribution discipline. Injected memory per turn should be small (production systems: ~1.5k-14k tokens, not 25k-115k), hybrid-scored (similarity + recency + importance, never similarity alone), bounded, with a hard rule that the contact's current message always outranks stored memory. Retention/sensitive-category handling must be purpose-tied (UAE PDPL, GDPR-baseline) with a hard exclusion list (health, ID/visa, financial numbers, religion, biometric, third-party/minor data) — build this yourselves; no memory vendor enforces it by default.

---

## 1. State of the art: structured facts vs summaries vs vector/episodic vs hybrid

**Intercom Fin** does RAG over past conversations + help content + integrations, with no disclosed structured per-user fact store or update cadence — least sophisticated architecture surveyed. [intercom.com](https://www.intercom.com/help/en/articles/9929230-the-fin-ai-engine) **ChatGPT memory** is hybrid: discrete user-visible "saved memories" + a separate undisclosed "chat history reference" layer over raw history. [help.openai.com](https://help.openai.com/en/articles/8590148-memory-faq) Documented failure (single-source, techbuzz.ai citing ZDNet): a wrong saved fact persisted with full confidence for months after the underlying reality changed. **Claude's memory tool** is the most concretely specified: a directory of plain files under `/memories`, client-hosted, the model itself issues `view/create/str_replace/insert/delete/rename` calls, nothing auto-injected ("just-in-time" retrieval), designed to survive context-editing/compaction. [platform.claude.com](https://platform.claude.com/docs/en/agents-and-tools/tool-use/memory-tool) **ManyChat** has no native long-term/AI memory, only unlimited custom fields (manual slot-filling) — confirmed by ManyChat's own community forum.

**MemGPT/Letta** ([arXiv:2310.08560](https://arxiv.org/abs/2310.08560)) is OS-inspired two-tier "virtual context management": main context (system prompt + fixed writable core memory + FIFO queue) + external context (searchable recall + unbounded archival), paged via the model's own function calls; ~2,093 tokens (~6.5% of a 32k window) overhead at init (single-source, [leoniemonigatti.com](https://www.leoniemonigatti.com/papers/memgpt.html)). **Mem0** ([github](https://github.com/mem0ai/mem0), [arXiv:2504.19413](https://arxiv.org/html/2504.19413v1)) has an LLM extract facts each turn, then a second LLM call decide ADD/UPDATE/DELETE/NOOP against the top-10 similar existing memories, over vector + optional graph + KV storage; its self-reported LOCOMO/LongMemEval benchmarks are contested by Zep's independent rerun — **treat cross-vendor LOCOMO comparisons as apples-to-oranges**. **Zep/Graphiti** ([arXiv:2501.13956](https://arxiv.org/abs/2501.13956)) is a temporal knowledge graph: every edge carries `valid_at`/`invalid_at` + `created_at`/`expired_at`; contradictions are **invalidated** (window closed), never deleted; reported DMR 94.8% vs MemGPT 93.4% is a single research group's own number, no independent replication. **LangMem** uses a semantic/episodic/procedural taxonomy, "hot path" (inline) vs "background" (async) extraction, and relevance = similarity + importance + recency decay ("strength"), but publishes no token/item budgets.

**Synthesis**: structured facts are cheap/auditable but lossy; rolling summaries decay under repeated compaction; vector/episodic memory captures nuance but risks stale/contradictory retrieval without invalidation. Rigorous systems converge on: small always-in-context structured core + external on-demand store, with graph-based systems (Zep, Mem0-graph) adding the temporal dimension that matters for a real-estate CRM ("when did the budget change" is itself signal).

---

## 2. Summary rewrite vs append

**LangChain/LangGraph rewrites, definitively**: `ConversationSummaryMemory`'s prompt ([source](https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain_classic/memory/prompt.py)) regenerates one scalar summary wholesale each turn — old string overwritten, not chunk-appended; the modern `SummarizationNode` ([docs](https://langchain-ai.github.io/langmem/guides/summarization/)) does the same, explicitly "overwrite[s] the previously generated summary." LangGraph pairs this with a separate long-term `Store` for discrete facts that *does* accumulate/update per item — i.e. LangChain already runs the two-tier split this report recommends. **Mem0's** contradiction resolution is an LLM tool-call decision informed by recency/timestamps, not a hard algorithm; a reported bug ([issue #4536](https://github.com/mem0ai/mem0/issues/4536)) shows the DELETE path sometimes dropping the old fact without correctly storing the new one — treat as version-dependent/unresolved. **Zep's** bi-temporal invalidation is the cleanest supersede mechanism — close the old edge's validity window, sort by `valid_at` for out-of-order arrivals: exactly the "2-bed→3-bed" case, old fact becomes closed-window history, retrieval filters to currently-valid edges only.

The sharpest evidence on rewrite risk (single paper, [arXiv:2605.12978](https://dylanzsz.github.io/faulty-memory/)): continuous LLM consolidation made agents **perform worse than no memory** in several settings (GPT: 100%→54% on previously-solved ARC-AGI problems after consolidating its own correct solutions); episodic-only (raw episodes, selective deletion, no abstraction) matched or beat every consolidator tested. Corroborated by a second source ([arXiv:2505.16067](https://arxiv.org/pdf/2505.16067)) on semantic drift from repeated summarization — drift risk cross-checked across 2 sources, exact regression numbers single-paper. Confidence decay is never the actual conflict-resolution mechanism anywhere surveyed — only a retrieval-ranking nicety (e.g. Mem0's optional access-based re-ranking).

**Synthesis**: not "pick one" — a two-tier split. A small capped rewritten-in-place summary is fine as a disposable current-conversation snapshot (regenerate rarely, e.g. at conversation close). Durable facts should be discrete, timestamped, and **superseded on write** (close old validity window, keep for audit) — never silently rewritten, never appended without a supersede step. The hardest sub-problem is implicit-conflict detection: the STALE benchmark ([arXiv:2605.06527](https://arxiv.org/abs/2605.06527)) found even the best frontier model only 55.2% accurate at preferring a newer fact over its stale, unstated-negation predecessor — needs an explicit application-level supersede rule, not reliance on the model inferring the contradiction unaided.

---

## 3. Fact extraction reliability

Per-turn/incremental timing is dominant (Mem0), but the recommended refinement (cross-checked, Mem0's voice-agent writeup + a general production guide) is **asynchronous, off the critical path** — retrieve before replying, write after the reply is sent. Cost is explicit: one guide's math (single-source) put naive full-quality per-turn extraction at ~$1,000/day at 10k users×10 msgs/day, driving cheap-model routing (~16x cheaper). Batch/end-of-session extraction appears mainly as a cost optimization, not anyone's primary choice.

Avoiding junk facts rests on two corroborated techniques: a salience/importance threshold below which nothing is written (Stanford Generative Agents' "poignancy" model, echoed by Mem0's eviction writeup, which flags "rater drift" when the scoring model/prompt changes — pin scorer versions), and prompting the extractor to return nothing for non-substantive turns (single-source, MemoryPlugin). No universal cutoff exists — tune recall/precision per use case; for a lead bot, budget/community/timeline/financing should sit well above the bar that filters small talk.

Confidence/provenance tracking is inconsistent, and notably absent where it matters most: Mem0's own paper explicitly states it does **not** describe confidence or provenance tracking, even though Mem0's product docs separately claim timestamp/hash/category/`attributed_to` metadata — **a direct paper-vs-docs discrepancy**; treat "Mem0 has full provenance" as marketing-level. Store source message ID, timestamp, and channel from day one — cheap now, expensive to retrofit.

ChatGPT's Manage Memories UI is the mainstream human-editable-store reference, but its failure mode (wrong fact persists with full confidence until manually noticed) is the single most concrete real-world failure case found (single-source account, though the general mechanism matches how the feature is known to work). Letta/MemGPT's editing is agent-self-editing via tool calls, not human-facing — a gap. Intercom's pattern (AI drafts a field update, human reviews/approves) is architecturally safer and the closer analog recommended below.

Known failure modes: hallucination on hypothetical statements is measured (dated 2023-era benchmark, HypoTermQA — rate not representative of current models, but the trap is real: "if I bought a villa..." must not become a stored ownership fact). Prompt-injection memory poisoning is a reproduced attack (Unit42 PoC against Bedrock Agents). Cross-speaker misattribution ("I want X" vs "my spouse wants X") is named across two independent sources — directly relevant since leads relay co-buyer preferences in the same thread.

---

## 4. Context injection: budget, relevance, failure modes

Vendor-reported per-turn budgets, vs. naive full-context: Mem0 ~7k tokens (~14k graph variant) vs. ~26k raw history; Zep ~1.6k tokens/2.6s vs. ~115k tokens/29s (same benchmark, single research group); MemGPT/Letta ~2.1k tokens (6.5% of a 32k window) at init, tiered rather than a flat injection; LangMem publishes no number, qualitative formula only. Mem0 also reports top-5 recall dropping 94%→71% as store size grows 100→10,000 items (single-source) — degradation persists even with retrieval discipline.

Every serious system uses hybrid relevance scoring (similarity + recency + importance), explicitly rejecting similarity-alone ("cosine-similarity bias favors semantic match over temporal recency, causing retrieval of outdated values" — Mem0). "Always inject one capped summary" appears only as the naive baseline everyone optimizes away from.

Failure modes: **bloat/"lost in the middle"** — the Stanford/UNC long-context paper found a U-shaped accuracy curve by position (worst-case GPT-3.5 accuracy fell *below* no-context baseline); Mem0's blog reports naive accumulation without retrieval discipline producing 80k-120k token contexts within 2-3 weeks of real usage. **Stale memory overriding current input** — "implicit conflict" (STALE, 55.2% best-case accuracy) and "manufactured confidence" (a hedged remark consolidated into a confident dated "fact") — both single-source, mechanistically distinct. **Memory poisoning** — formally named in OWASP's Agentic Top 10 2026; real case: ChatGPT memory exploitable via indirect prompt injection to write false persistent facts, corroborated across 3 sources (Embrace the Red, Dark Reading, LearnPrompting).

Documented mitigations: an explicit precedence rule (current message > session memory > older global facts, most-recent-by-date wins) recurs across independent engineering write-ups (community practice, not a vendor standard); confidence decay at read time (one system: 30-day half-life, single-source number); hard token ceilings with an explicit "N memories omitted" note, not silent truncation; for poisoning specifically — never let untrusted external content silently trigger a memory write, require confirmation for destructive memory ops, cap inserts per interaction.

---

## 5. Privacy and data-hygiene norms

**Meta platform policy** ([Cloud API Terms](https://www.facebook.com/legal/WhatsApp-Business-Platform-Cloud-API), [dev docs](https://developers.facebook.com/documentation/business-messaging/whatsapp/data-privacy-and-security/), [Business Data Policy](https://whatsappbusiness.com/policy/)): message content retained by Meta max 30 days; phone-number identifiers deleted within 30 days of last status update; post-termination data deleted within 90 days. Meta prohibits using WhatsApp-obtained data beyond messaging that person, prohibits requesting/sharing full financial or government ID numbers, prohibits telemedicine/health-info use, prohibits sharing one customer's chat with another. **Correction to a common assumption**: the 24-hour/7-day service window governs what you may *send and when* — it does not govern data storage/retention; that's a separate clock from your own database (confirmed across 3 sources).

**UAE PDPL (Federal Decree-Law 45/2021)** — text via legaladviceme.com; cross-check article numbers against an official gazette before finalizing. No fixed retention period — Art. 5(7): data may not be kept past its processing purpose (purpose-tied, not calendar-based). Minimization/purpose limitation: Art. 5(2)-(3). Consent must be clear, revocable, provable: Art. 6. Sensitive categories (Art. 1): race/ethnicity, political/philosophical opinion, religious belief, criminal record, biometric data, health status. Rights: access (13), correction (15.1), erasure (15.2). **Needs verification, not settled**: PDPL doesn't apply in DIFC/ADGM free zones (separate GDPR-aligned regimes) — confirm which jurisdiction the business entity sits in.

**GDPR baseline**: minimization/purpose limitation mirror PDPL closely. Right-to-erasure guidance treats soft-delete flags as non-compliant ("verifiable and irreversible," Art. 17) — implement hard-delete of identifying rows, with anonymization where relational integrity matters, and a legal-hold carve-out for statutory-retention data (tax/finance) in a separate record class.

**Sensitive-fact exclusion lists**: Mem0's docs ([controlling-memory-ingestion](https://docs.mem0.ai/cookbooks/essentials/controlling-memory-ingestion)) show an opt-in STORE/NEVER-STORE pattern (SSNs, insurance numbers, full addresses, financial identifiers, hedged statements) — but **Mem0 runs no PII scan by default**; exclusion is developer-configured, not built in. No memory vendor filters sensitive data for you. Cross-source convergence (Meta policy + PDPL Art. 1 + Mem0 docs + [MIT Tech Review](https://www.technologyreview.com/2026/01/28/1131835/what-ai-remembers-about-you-is-privacys-next-frontier/)) on categories to exclude: health/medical, government ID/passport/visa/immigration status, financial account/card numbers, religion, political opinion, sexual orientation, biometric data, precise real-time location, and any data about an identifiable third party or minor.

---

## Recommendation for channels-manager

Architecture guidance, not an implementation plan — sized for the existing 2-3 person team, NestJS/BullMQ/Postgres/Prisma stack, no new infra unless justified.

**Store shape** (Postgres via Prisma, two tables — the two-tier pattern every mature system converges on):
- `ContactMemoryFact` — discrete, superseded facts: `contactId`, `key` (e.g. `budget_max`, `preferred_community`, `bedroom_count`, `timeline_to_buy`, `financing_status`), `value`, `confidence`, `sourceMessageId`, `channel`, `validFrom`, `validTo` (null = current), `createdAt`. A new fact for an existing key closes the prior row's `validTo` instead of deleting/overwriting — the Zep-style supersede, cheap in plain SQL, gives a free audit trail for "when did the budget change."
- `ContactMemorySummary` — one row/contact, short prose cache (few-hundred-token cap), **rewritten in place**, regenerated only at conversation boundaries (session close / N-hours idle), never per-turn. Disposable — a cheap "catch me up" cache derived from facts, never the source of truth, rebuildable if it drifts.

This avoids both failure modes found in research: rewrite-drift (summary is capped/low-stakes) and append-bloat/staleness (facts have explicit supersede).

**Update triggers**: extraction per-turn, asynchronous (BullMQ job after reply is sent, matching existing worker architecture, no added response latency); cheap model, not frontier; extraction prompt requires (a) a fixed field schema — a seeded/editable config list, not a hardcoded enum, per the project's data-driven-not-hardcoded rule, (b) attribution discipline ("only extract what THIS contact states about themselves"), (c) a salience gate (no small talk, hedged/hypothetical statements, single low-confidence mentions). Same-key contradiction: close old row, insert new — a deterministic supersede, not an LLM judgment call, avoiding the exact bug class seen in Mem0's issue #4536.

**Rewrite policy**: only `ContactMemorySummary` is ever rewritten wholesale, only at conversation-boundary cadence. Facts are never rewritten, only superseded.

**Injection policy**: inject the current summary (~300-500 tokens) + currently-valid facts (`validTo IS NULL`) relevant to the active flow — flat SQL filter, no vector search needed at this scale (tens of facts per contact, not thousands). Hard ceiling ~800 tokens total; if exceeded, prioritize by recency and log what was omitted, never truncate silently. Non-negotiable system-prompt rule: the contact's current message always overrides stored memory on conflict. Financial/legal/commitment claims ("I already paid the deposit") never silently become a stored fact — flag for human review.

**Bloat caps**: summary has a hard token cap enforced at generation; facts have no row-count cap (cheap, superseded not accumulating) but retention-driven pruning applies.

**Operator-edit surface**: a per-contact "memory" panel in the existing admin UI — current facts (editable/deletable), full superseded history for audit, summary (regeneratable on demand). Satisfies PDPL correction/erasure and avoids the research's starkest lesson: ChatGPT's worst failure is a wrong fact persisting silently until a user notices — cheap to avoid by making memory legible from day one. Deletion is a hard row delete, not a soft-delete flag, per GDPR's "verifiable and irreversible" standard.

**Retention and exclusion**: tie retention to relationship status, not a flat calendar (active-lead facts retained through the relationship + 12-24 months inactivity tail, then auto-review/anonymize/delete) — decoupled from Meta's own 30/90-day platform retention (unrelated clock). Hardcode a never-extract exclusion list at the extraction-prompt level (health/medical, government ID/passport/Emirates ID/visa status, financial account/card numbers, religion, political opinion, sexual orientation, biometric data, precise real-time location, third-party/minor data) as a small seeded config table, not a literal code array. Confirm mainland vs DIFC/ADGM jurisdiction before finalizing retention numbers — a one-line legal confirmation, not further research.

**Tradeoffs**:
- *Pro*: no new infrastructure (no vector/graph DB) — fits the team and stack; deterministic supersede avoids Mem0-style LLM-adjudication bugs; operator-editable from day one avoids the worst documented failure mode; bounded token cost per turn.
- *Con*: no semantic/vector retrieval — relevance is recency + explicit flow match, not similarity-scored; acceptable at this data scale but would need revisiting if scope grows to cross-contact semantic search or much larger per-contact fact volumes.
- *Con*: async post-turn extraction means a fact stated in message N isn't available until N+1 at earliest — matches the dominant production pattern, but an explicit limitation, not an oversight.
