Agent memory is a write problem, not a retrieval problem

By September 26, 2026AI
Agent memory is a write problem, not a retrieval problem

Follow one fact through an agent’s memory: how it gets written, why it quietly goes stale, what it takes to retire it, and the template that makes the whole thing repeatable from one client to the next.

Key Takeaways

  • Most memory failures start on the write side. Retrieval usually works fine. The real problem is that corrections never become facts and outdated records are never retired.
  • Supersession keeps answers current. Marking old records invalid with validity windows, instead of deleting them, stops stale facts from competing and still answers point-in-time questions.
  • Tenant isolation belongs in the storage, not the query. Filters protect queries, but derived artifacts like summaries and caches can still leak data unless every one carries its tenant tag.
  • A shared record schema makes memory repeatable. With one record contract and a per-client configuration file, the next deployment becomes configuration rather than a rebuild.

Introduction

Month four. Northwind Industrial runs its service contracts on a multi-tenant SaaS platform, and that platform ships an operations copilot. An account manager asks who the renewal for the Acme North account should go to. The copilot answers immediately: Dana Reyes, billing contact, and it helpfully includes her email address.

Dana left Acme in March. The account manager had said so, to this copilot, in a Tuesday conversation that went fine at the time. The correction is still sitting there, six turns into a transcript the system dutifully stored and never looked at again.

Retrieval did not fail. It ran, it scored, it returned the highest scoring record about billing contacts at Acme North, and that record was true. It was true in January.

About this scenario
The account, the vertical, and the names are a composite drawn from several engagements. The architecture, the failure modes, and the timelines are the real ones.

This is the shape of nearly every agent memory problem we get called into. The retrieval layer is competent. The write path never converted a correction into a fact, never retired the record that fact contradicted, and had no mechanism by which it could have. Teams then spend their engineering budget tuning rerankers, which is the one place where there was nothing wrong

The structural reason is worth naming. Retrieval augmented generation was designed for a corpus somebody else curated and froze, so the hard problems are chunking and ranking. Memory is a corpus the agent writes to, continuously, from noisy conversational input, where old records stop being true and nobody retires them. Same retrieval machinery, completely different failure surface.

What follows tracks a single claim, the billing contact for Acme North is Dana Reyes, from the turn that created it to the day the copilot finally answers correctly. Around that thread sits the architecture: the memory classes worth separating, the write pipeline that turns transcripts into retrievable facts, the read pipeline that decides what earns space in the prompt, and the configuration template that lets the same design ship to the next client in days rather than months. Implementation notes cover RAGFlow, which now has a native memory module, plus the equivalent stacks you would choose instead.

Four ways this breaks, and why none of them are bugs

Nothing in that opening scene was a defect. Every failure below is the structural consequence of a decision that looked entirely reasonable when it was made.

1. The agent retrieves what sounds similar, not what is true

The Tuesday turn contained a greeting, a scheduling aside, a complaint about a delayed shipment, and one sentence retiring Dana. Embedded whole, the vector represents the average of those things, and that average does not look much like a question about billing contacts. Raw turns embed badly because a turn is not a unit of meaning. Retrieval quality collapses well before the corpus gets large.

2. Nothing ever becomes false

The January record naming Dana was never marked wrong, because nothing in the system was capable of marking it wrong. Both the January statement and the March correction sit in the store, both embed near the query, and the reranker picks on semantic similarity, which cannot distinguish current from superseded. The agent answers with whichever scored higher. Without an explicit supersession mechanism, memory accuracy degrades monotonically as the store grows. Month one looks great. Month twelve is the problem.

3. Memory eats the context window

The team’s first fix was to raise top-k from 5 to 20, on the theory that the March turn would now make the cut. It did. The copilot saw both the March turn and the January record, and answered with January anyway, at roughly three times the prompt cost. Prompt size climbs, latency climbs, and answer quality often drops, because the model now has to find the relevant fact inside a wall of loosely related ones. Memory without a token budget is a cost line that grows with tenure.

4. Memory leaks across tenants

This one did not surface in month four. It surfaced during the rebuild, and it is covered in full further down, because it is the failure with actual legal weight. The short version: isolation was a metadata filter applied at query time, and a nightly job that summarized across tenants never went through the query path at all. Filters protect the query. They do not protect anything the system derived from more than one tenant’s data.

The takeaway

Three of the four happen on the write side, and the fourth happens outside the query path entirely. The leverage sits in what gets written, in what shape, and with what lifecycle. Design the write path first, and the retrieval work that follows is ordinary engineering.

Where should the Dana claim have lived?

Ask that question and the taxonomy answers itself. The claim is a stable attribute of an account, so it belongs in a semantic store where records are superseded rather than aged out. The Tuesday conversation that produced it is raw, kept for audit. The fact that a contact changed hands in March, and that the renewal slipped as a result, is episodic. The account manager’s standing instruction to always copy the regional lead on renewals is procedural.

None of those distinctions existed in the month four system. Everything went into one store, which is the root of most retrieval noise. Cognitive science offers a taxonomy that turns out to be operationally useful here, and it is the same split RAGFlow adopted in its memory module. We run five layers.

Class What it holds Write trigger Lifetime Store
Working The live turn window, scratchpad, tool results in flight Every turn Session Runtime state, Redis
Raw Verbatim transcript and tool call log Every turn, append only Retention policy Postgres or object storage
Semantic Stable facts, preferences, entity attributes Post turn extraction Until superseded Vector plus relational
Episodic Events with time and outcome, “what happened when” End of task or session Decays with age Vector plus time index
Procedural How this user or org wants things done, playbooks, corrections On correction or success Versioned, long lived Vector plus version table

The separation earns its keep in three places. Retrieval can query only the classes a given question needs, so “who is the billing contact” never competes against a year of episodic noise. Each class gets its own decay rule, so a slipped renewal from last March fades while a billing contact does not. And deletion becomes tractable, because a request to erase a person maps to a set of records rather than to “somewhere in the embeddings.”

Knowledge base RAG sits alongside these, not inside them. Product documentation, policies, and contracts are authored content with an owner and a review process. Keep that lane separate and retrieve from it in parallel. Mixing authored knowledge with agent-generated memory in one index makes provenance impossible to reason about, and provenance is what you need when a regulated client asks why the agent said what it said.

Where should the Dana claim have lived?
Figure 1. The memory stack. The Dana claim belongs in semantic, the Tuesday transcript in raw, the slipped renewal in episodic. Working memory lives in the runtime. Knowledge base RAG runs as a parallel lane so provenance stays clean.

The write path is the product

Here is the pipeline that should have run when that Tuesday turn completed, and did not. It is asynchronous, it is idempotent, and every stage is a place you can raise or lower quality deliberately. Stage 4 is the one that was missing.

The write path is the product
Figure 2. The write path. Stages 2 through 4 are where memory quality is won or lost, and stage 4 is the one the month four system did not have. Most teams skip straight from capture to embed, which is why their memory retrieves noise and never retires anything.

Extraction: atomic, self contained, typed

The extractor takes a turn and emits zero or more claims. Run the Tuesday turn through it and the sentence “Marcus Chen is taking over billing, Dana’s last day is Friday” should come out as two separate records, one naming the new contact and one dating the departure. Three rules make that reliable.

  • Atomic. One claim per record. The Marcus sentence is two, because a query about who to invoice should not have to match against a sentence that is half about a departure date.
  • Self contained. No pronouns, no “the same as before.” A claim that reads “he is taking over billing” is worthless at retrieval time, because the turn that named him is not in the prompt. The record has to say Marcus Chen, at Acme North, for billing.
  • Typed at write. The extractor assigns the class, the subject entity, and a confidence. Classifying at read time means running an LLM call inside the latency budget of every request, which nobody does twice.

Salience scoring is the cheapest quality lever available. Ask the extractor a second question about each claim: will this still matter in thirty days, and would an agent need it to answer a future question. The Marcus claim passes both. The complaint about the delayed shipment passes neither, and writing it to the semantic store only means it competes for a top-k slot later. Most conversational content fails both tests. Writing only what passes typically shrinks the store by a large factor and improves retrieval precision at the same time, because precision is largely a function of how much irrelevant content is competing for those slots.

Conflict resolution and the memory lifecycle

This is stage 4, and it is where month four was decided. When a new claim arrives, compare it against the nearest existing records for the same subject and predicate. Four outcomes, and each needs an explicit rule.

Outcome Condition Action
Duplicate Same subject, same predicate, same value Increment reinforcement count, update last seen, write nothing new
Refinement Same subject and predicate, more specific value Supersede the old record, link it as the predecessor
Supersession Same subject and predicate, different value, newer source Mark old record invalid from the new timestamp, keep it for audit
Contradiction Different value, similar confidence, unclear ordering Hold both, flag for review, exclude both from retrieval until resolved

The Marcus claim lands squarely in row three. Same subject, same predicate, different value, newer source. The January record naming Dana gets a valid_to of March 14 and stops being retrievable as current, in the same transaction that writes the new one.

Notice that nothing is deleted. Records carry a validity window with a valid_from and an optional valid_to, which is what makes point in time questions answerable. “Who was the billing contact when we issued the February invoice” becomes a filtered retrieval rather than a guess, and in a dispute that distinction is the whole ballgame. This bi-temporal pattern (transaction time plus validity time) is the single most valuable structural addition to a memory store, and it is the core of what temporal knowledge graph products like Graphiti provide.

Conflict resolution and the memory lifecycle
Figure 3. Records move through states rather than being created and forgotten. The Dana record moves to superseded, not deleted, which is why February invoice questions still answer correctly. Dormant records can be revived by a hit, and deletion leaves a tombstone so downstream caches can be invalidated.

The read path: retrieval is a budgeting problem

With supersession in place, the January record is excluded in the semantic lane before anything is scored, so it never competes for a slot and no amount of similarity can resurrect it. That is the whole fix for month four, and it happened on the write side. What is left is routine engineering, plus the part teams underinvest in: deciding what actually earns a place in the prompt.

The read path: retrieval is a budgeting problem
Figure 4. The read path. The superseded Dana record is filtered out in the semantic lane before scoring, not after. Class routing runs lanes in parallel, fusion reranking merges them, and a fixed token envelope keeps memory cost flat as the store grows.

Four rules that make retrieval hold up

  • Hybrid, always. Dense vectors miss exact tokens (order numbers, SKUs, error codes) and lexical search misses paraphrase. Run both and fuse. Reciprocal rank fusion is a reasonable default and needs no score calibration.
  • Filter before you search, not after. Tenant, subject, class, and validity should be predicates pushed into the index, not a post-filter over top-k. Post-filtering silently degrades recall, because the filtered-out results already consumed the slots.
  • Fix the envelope, vary the contents. Give memory a fixed token budget per turn, split across classes. When the store grows, the competition inside the envelope gets tougher, and cost stays flat. This is the difference between a system that stays economical at year three and one that does not.
  • Render provenance into the prompt. Each memory line carries a record id, a validity date, and a confidence. It costs a handful of tokens and it lets the model say “as of March” instead of asserting stale facts as current, and lets you trace any output back to a record.

The memory record: one contract, every deployment

Here is the record the March turn should have produced. Note supersedes: it points at the Dana record, which is still in the store, still carries its own validity window, and still answers February questions correctly.

Templatization starts here. If the shape of a memory is identical across clients, every downstream component (extractor, resolver, retriever, evaluator, admin console) is reusable, and a new engagement becomes configuration rather than construction.

{
  "record_id": "mem_01J8X4K2P",
  "tenant_id": "northwind",
  "subject_id": "acct_4412",
  "class": "semantic",
  "predicate": "billing_contact",
  "claim": "Billing contact for Acme North is Marcus Chen, [email protected].",
  "structured": { "name": "Marcus Chen", "email": "[email protected]" },
  "confidence": 0.91,
  "salience": 0.82,
  "valid_from": "2026-03-14T16:41:00Z",
  "valid_to": null,
  "supersedes": "mem_01J2Q9F7B",
  "source": {
    "turn_id": "turn_88213",
    "session_id": "sess_5521",
    "channel": "ops_copilot"
  },
  "provenance": {
    "extractor_version": "v4",
    "prompt_hash": "9c1f0a",
    "model": "claude-sonnet-4-6"
  },
  "sensitivity": "pii",
  "reinforcement_count": 3,
  "last_retrieved_at": "2026-09-09T14:03:00Z",
  "state": "active"
}

On Postgres with pgvector this maps to a single table plus indexes, which is where we start most engagements because the operational burden is close to zero and it can carry a surprising amount of load.

CREATE TABLE memory_record (
  record_id           text PRIMARY KEY,
  tenant_id           text NOT NULL,
  subject_id          text NOT NULL,
  class               text NOT NULL,
  predicate           text,
  claim               text NOT NULL,
  structured          jsonb,
  embedding           vector(1024),
  claim_tsv           tsvector GENERATED ALWAYS AS (to_tsvector('english', claim)) STORED,
  confidence          real NOT NULL DEFAULT 0.5,
  salience            real NOT NULL DEFAULT 0.5,
  valid_from          timestamptz NOT NULL,
  valid_to            timestamptz,
  supersedes          text REFERENCES memory_record(record_id),
  source              jsonb NOT NULL,
  provenance          jsonb NOT NULL,
  sensitivity         text NOT NULL DEFAULT 'none',
  reinforcement_count int NOT NULL DEFAULT 1,
  last_retrieved_at   timestamptz,
  state               text NOT NULL DEFAULT 'active'
) PARTITION BY LIST (tenant_id);

CREATE INDEX ON memory_record USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);
CREATE INDEX ON memory_record USING gin (claim_tsv);
CREATE INDEX ON memory_record (tenant_id, subject_id, class, state);
CREATE INDEX ON memory_record (tenant_id, predicate, valid_from DESC);

Partitioning by tenant is deliberate, and it is the first half of the answer to the leak described further down. It turns isolation into a physical property of the storage rather than a predicate someone has to remember to write, and it makes tenant offboarding a partition drop instead of a long running delete that leaves index debris behind.

The memory pack: what makes this templatized

Northwind is one tenant on one platform. The next engagement is a different vertical with different decay rates and a different definition of sensitive. The architecture above does not change. A configuration file does. We call it a memory pack, and it is the deliverable that turns a bespoke build into a repeatable one.

# memory-pack.yaml
pack: support-agent-memory
version: 2.3
tenant_strategy: namespace_per_tenant   # namespace | database | cluster

classes:
  semantic:
    enabled: true
    half_life_days: null          # stable facts do not decay
    retrieval_budget_pct: 32
    extraction_confidence_floor: 0.7
  episodic:
    enabled: true
    half_life_days: 45
    retrieval_budget_pct: 23
    extraction_confidence_floor: 0.6
  procedural:
    enabled: true
    versioned: true
    retrieval_budget_pct: 26
    promote_after_repeats: 2
  raw:
    enabled: true
    retention_days: 400
    retrievable: false            # audit only, never retrieved

retrieval:
  token_envelope: 2000
  headroom_pct: 19
  mode: hybrid                    # dense + bm25, reciprocal rank fusion
  rerank: cross_encoder
  relevance_floor: 0.34
  filters_pushed_down: [tenant_id, subject_id, class, state, valid_window]

write_policy:
  salience_gate: true
  conflict: supersede_on_newer_source
  contradiction_action: hold_and_flag
  review_sample_rate: 0.05

governance:
  sensitivity_classes: [none, pii, phi, financial]
  redact_before_embedding: [phi]
  erasure: cascade_to_derived
  audit_retention_days: 2555

models:
  embedding: text-embedding-3-large
  extractor: claude-sonnet-4-6
  reranker: bge-reranker-v2-m3

Everything a client engagement actually varies (which classes matter, how fast things go stale, what counts as sensitive, how much context the budget allows) lives in this file. The pipeline code does not change. Neither does the evaluation harness, the admin console, or the deletion flow. That is what makes the second deployment cost a fraction of the first, and it is the difference between selling an AI project and having a practice.

The memory pack: what makes this templatized
Figure 5. One pack definition, one set of stateless services, N isolated tenant namespaces. Per tenant overrides sit in the same schema, so a client-specific requirement never forks the codebase.

Implementing on RAGFlow

So where does this get built. RAGFlow added a native Memory module in version 0.23.0, and it maps closely onto the model above, which makes it a fast way to stand up the agent-facing layer without writing a pipeline from scratch.

What RAGFlow gives you

  • Typed memory out of the box. Four types: raw (required, the verbatim conversation), semantic (stable facts, preferences, attributes), episodic (time and event bound records), and procedural (processes, habits, handling rules). Raw is the substrate the others are extracted from.
  • Two models per memory. An embedding model for retrieval and a large language model for extraction, configured on the memory itself. Changing them after messages exist is not recommended, which matters for planning: pin your embedding model before you load production data, or budget for a reindex.
  • Extraction prompts you control. The system prompt defines the extraction role, output format, and structural constraints, and temperature (0 to 1) controls how stable the extraction is. Low temperature is the right default: you want the same claim phrased the same way every time so dedupe works.
  • Agent wiring through components. The Message component writes to a memory via its save setting, and a Retrieval component reads from it. Both directions need to be configured explicitly; a memory that is written but not wired to a Retrieval component is never recalled.
  • Visibility scope. Memories are scoped to “only me” or to the team, which is a useful coarse boundary for internal agents.
  • Message-level control. Individual messages can be excluded from retrieval, and entries that have been actively forgotten stop appearing in agent calls and are prioritized for removal when the forgetting policy fires.

Where you will need to build around it

Capacity. Memory size is capped, with a maximum in the low single digit megabytes per memory, and the forgetting policy on offer is first in first out. FIFO is time ordering, not value ordering. On a busy tenant, a billing contact learned in week one is evicted before a throwaway shipping complaint from yesterday, which is the same class of failure as month four arriving from the opposite direction. For an assistant that accumulates knowledge over years, it is the wrong curve.

Storage type. The current storage option is table oriented, which suits general messages and field queries. Graph-shaped memory, where the value is in traversing relationships between entities, is not what this module is for.

Validity windows. There is no bi-temporal validity model, so point in time questions and clean supersession need to be modeled in your own layer.

The pattern we use is a split. RAGFlow Memory serves as the hot, agent-facing working and session layer, where its size cap is a feature rather than a limitation because it enforces a bounded context. The durable, tenant-scale semantic and procedural store lives outside it in Postgres with pgvector, or in the vector database the client already runs, and is exposed to the RAGFlow agent as a retrieval tool. Consolidation runs on a schedule: promote what proved durable in RAGFlow Memory into the external store before FIFO gets to it, and pull the top ranked external records back in at session start.

Two more RAGFlow features are worth designing around. The Agent API returns execution trace logs, which is what you want feeding your evaluation harness rather than reconstructing agent behavior from application logs. And on the knowledge base lane, parent-child chunking plus table of contents extraction address the recall-versus-completeness tension directly: retrieve on the small child chunk, hand the model the parent. Use both on document-heavy deployments.

Equivalent stacks and when to pick them

RAGFlow is one option. The memory layer market has consolidated into a few architectural bets, and the right choice follows from which problem dominates your deployment.

Stack Architectural bet Pick it when Main tradeoff
Postgres + pgvector You own the schema, the lifecycle, and the indexes Regulated data, existing Postgres operations, custom conflict rules, full portability You build extraction, decay, and consolidation yourself
RAGFlow Converged RAG and agent platform with memory as a module Document-heavy agents where knowledge base RAG is the dominant workload and memory is secondary Capacity cap and FIFO forgetting; no validity windows
Mem0 Extract and retrieve as a drop-in API across frameworks Fast path to per-user personalization; framework-agnostic; small context footprint Timestamps without true point in time reconstruction; graph features tiered
Zep / Graphiti Temporal knowledge graph with fact validity windows Entity relationships that change over time, contradiction resolution, auditability Heavier context footprint and graph infrastructure; hosted-first
Letta Agent-managed memory, main context as RAM and archival as disk Long-horizon autonomous agents that should curate their own memory Adopting a full agent runtime, plus tool-call overhead per session
LangGraph + LangMem Memory primitives native to an existing orchestration graph You are already on LangGraph and want the least integration friction Couples memory to the framework; backend performance is your problem
On benchmarks

Every vendor in this space publishes memory benchmark scores, commonly on LongMemEval. Those numbers move substantially when reproduced under independent harnesses, and the independent harnesses are frequently run by competing vendors. Treat published scores as directional evidence about architecture, not as a procurement input. Build a golden set from your own traffic and measure on that. It takes about a day and it is the only number that predicts your outcome.

Multi-tenant isolation, done properly

The near miss on this engagement had nothing to do with Dana going stale. A nightly job summarized recent account contact changes across the whole corpus to warm a cache, keyed on the text of the query rather than on the tenant. Marcus Chen’s email address was one cache hit away from appearing inside a different distributor’s answer. No retrieval filter was violated, because the leak path was never a retrieval.

This is where memory systems create real liability, and where retrieval-time filtering quietly fails. Four requirements.

Isolate at the index, not the query

Namespace per tenant, partition per tenant, or database per tenant depending on how many tenants and how strict the requirement. Credentials scope to the namespace. A query that forgets its tenant predicate should return an authorization error, not another tenant’s data.

Tag derived artifacts at creation

The leak path that filters do not cover is derivation, which is exactly what the nightly job was. A summarization job that reads across tenants to warm a cache, an evaluation set assembled from mixed traffic, a fine-tuning corpus, an embedding cached against text alone: all of these can carry tenant A content into a tenant B response without any query ever violating a filter. Every derived artifact inherits the tenant tag of every input, and an artifact carrying two tenant tags is a bug that should fail the build.

Make erasure cascade

A deletion request has to reach the record, its embedding, its supersession chain, every summary that consumed it, and every cache entry keyed on it. Model this before you launch, not after the first request arrives. The cascade is much easier to implement when every record carries its source ids from day one, which is the other reason the provenance block in the record schema is not optional.

Probe for leaks in CI

Plant a canary fact in tenant B, something shaped like a contact record so it exercises the same paths Marcus does. In CI, query with tenant A credentials for that fact, across every retrieval path and every derived artifact including the summarization and evaluation jobs. Fail the build on any hit. This is a half day of work and it is the only isolation control that keeps working as the codebase changes.

Measuring whether it works

Contradiction rate is the metric that would have surfaced month four in week two, for the cost of an afternoon. Four metrics on a frozen golden set built from real traffic, tracked per release.

Metric Definition Why it matters
Recall at k Share of queries where the required fact appears in the retrieved set The ceiling on answer quality. If it is not retrieved, no prompt fixes it.
Contradiction rate Share of responses asserting something the store disproves Directly measures whether supersession is working
Staleness rate Share of served records with a valid_to in the past Catches decay and forgetting failures before users do
Context cost Median memory tokens per turn Tells you whether the envelope is holding as the store grows

Add one qualitative check that catches things metrics miss: sample twenty memory records a week and ask whether a human reading them cold would agree they are true, atomic, and worth keeping. Extraction quality drifts when models or prompts change, and it drifts silently.

Anti-patterns we see repeatedly

  • Embedding raw turns. Cheap to build, degrades immediately, and hard to unwind once the store is large. Extract.
  • Raising top-k to fix precision. More retrieved records means more competing content and a worse answer. Fix ranking and salience instead.
  • Letting the agent write memory synchronously mid-turn. It adds latency to every turn and puts extraction failures on the user’s critical path. Write asynchronously after the turn.
  • Treating summarization as consolidation. A rolling summary loses the structure that makes records queryable and superseded. Summaries are a compression technique, not a memory model.
  • No forgetting policy. Every store needs a decay rule and a floor. Unbounded growth is a cost problem first and a quality problem second.
  • Choosing the stack before defining the record. The record schema is the decision that constrains everything else. Pick it first, then pick the store that serves it.
  • A realistic rollout

    Six weeks is the honest timeline for a multi-tenant memory layer with write actions and an evaluation harness. Here is how that splits.

    Phase Scope Exit criteria
    Weeks 1 to 2 Record schema, raw capture, extraction v1, hybrid retrieval, single tenant, read only recall Golden set built, recall at k measured, extraction precision reviewed by hand
    Weeks 3 to 4 Conflict resolution, validity windows, decay and forgetting, token envelope, admin view of records Contradiction and staleness rates under target, context cost flat under load
    Weeks 5 to 6 Tenant namespacing, erasure cascade, CI leak probes, memory pack config, one guided write action behind approval Leak probe green, erasure verified end to end, second tenant deployed from config alone

    The sequencing matters. Teams that start with multi-tenancy and write actions spend weeks on infrastructure before they learn whether their extraction is any good, and extraction quality is the thing that decides whether the project succeeds. If the extractor cannot reliably turn the Tuesday turn into two clean claims, nothing downstream saves it.

    Month twelve

    Same account, same question, rebuilt system. An account manager asks who the Acme North renewal should go to, and the copilot answers:

    Billing contact for Acme North is Marcus Chen, [email protected], current as of March 14. Before that it was Dana Reyes. Standing instruction on this account is to copy the regional lead on renewals.

    That is three sentences and it is the entire architecture reporting for duty. The contact came from the semantic store, current because supersession retired its predecessor rather than leaving both to compete. The March date came from a validity window, which is also why the February invoice question still resolves to Dana. The standing instruction came from the procedural layer, promoted after the account manager said it twice. Nothing else from twelve months of conversation made it into the prompt, because it did not clear the salience gate or did not win a slot inside the envelope. Marcus stayed inside the Northwind namespace throughout, including in the nightly summarizer, because the leak probe in CI would have failed the build otherwise.

    None of that is a retrieval achievement. Every one of those properties was decided on the write side, weeks before the question was asked.

    Frequently Asked Questions

    What is the difference between RAG and agent memory? +

    RAG retrieves from a corpus someone else authored, such as policy documents or product manuals. Agent memory retrieves from a corpus the agent itself wrote during past interactions. RAG is read-only against curated content, so the hard problems are chunking and ranking. Memory is read-write against generated content, so the hard problems are extraction quality, contradiction handling, and forgetting. They share a retrieval layer but need different write paths and different governance.

    Can I just store every conversation in a vector database and call it memory? +

    No. Raw transcripts embed poorly because a single turn mixes pleasantries, corrections, and facts into one vector. Retrieval returns turns that sound similar rather than facts that are true, and stale statements are never retired. Extract atomic, self-contained claims from transcripts, type them, resolve contradictions against existing records, and apply a decay policy. Keep the raw transcript for audit, but retrieve against the extracted records.

    Does RAGFlow support agent memory natively? +

    Yes. RAGFlow added a Memory module in version 0.23.0. It supports four memory types, with raw required and semantic, episodic, and procedural available as extracted layers. Each memory has an embedding model for retrieval, a large language model for extraction, a configurable size cap, and a first in first out forgetting policy. Agents write through the Message component and read through the Retrieval component. The built-in memory is capped per memory, so tenant-scale durable stores usually sit outside it.

    How do you keep memory isolated between tenants? +

    Isolate at the index or collection level, not at query time with a metadata filter. A filter is one forgotten predicate away from a cross-tenant leak, and it does not protect derived artifacts such as summaries, embedding caches, or evaluation sets that mixed tenant content during generation. Give every tenant its own namespace, scope credentials to it, tag every derived artifact with the tenant identifier at creation, and run an automated leak probe in CI.

    How do you measure whether agent memory is working? +

    Measure four things on a frozen golden set: recall at k, contradiction rate, staleness rate, and context cost in tokens per turn. Track them per release. Public benchmarks such as LongMemEval are useful for calibrating an approach, but vendor published scores frequently fail to reproduce under independent harnesses, so build your own golden set from real traffic.

    How long does it take to build a production agent memory layer? +

    For a single tenant with read-only recall, two to three weeks is realistic. For a multi-tenant deployment with write actions, extraction quality gates, contradiction handling, deletion and revocation flows, and an evaluation harness, six weeks is the honest number. The retrieval layer is rarely the bottleneck. Extraction prompts, conflict rules, and the isolation and deletion work consume most of the schedule.

    Raj Sanghvi

    Raj Sanghvi is a technologist and founder of Bitcot, a full-service award-winning software development company. With over 15 years of innovative coding experience creating complex technology solutions for businesses like IBM, Sony, Nissan, Micron, Dicks Sporting Goods, HDSupply, Bombardier and more, Sanghvi helps build for both major brands and entrepreneurs to launch their own technologies platforms. Visit Raj Sanghvi on LinkedIn and follow him on Twitter. View Full Bio