Memory coherence in MCP: what happens when two clients write
You open the same project in two editors. Both have your memory server connected. In the morning one of them learns that the deploy target moved to a new region and writes that down. In the afternoon the other one, still holding what it read at breakfast, corrects the same fact back to the old region.
Which one wins? What does the loser see? Does anybody find out?
The protocol does not say. That is not a defect, and this piece is not a complaint. It is a scope question with a practical consequence: coherence is a property each memory server chooses, and the shipped servers have chosen differently.
TL;DR
- MCP defines no memory-level ordering, conflict detection or merge semantics. Each call is served independently.
- Revision
2026-07-28removed protocol-level sessions. No handshake, every message its own POST, and a modern server SHOULD ignore a legacyMcp-Session-Idheader rather than honour it.- The spec does police one kind of disagreement, between a header and a body, and says why: two components must not rely on "different sources of truth". Memory-level disagreement is simply out of its scope.
- Shipped answers exist. Anthropic offers an optimistic precondition on a content hash. Mnemoverse rejects, on its REST write path, a correction whose target was claimed in the meantime. Both make a race fail loudly instead of silently.
- Staleness is a product property too: one vendor documents a 15-second default sync interval between a self-hosted worker and its store.
Quotes below were read from live specification and vendor pages on 2026-09-03 and 2026-09-04.
Coherence, in the sense used here
Memory coherence is the question of what two readers see after two writers touch the same stored fact. It is a different question from ranking, which is about which memories come back first, and from temporal validity, which is about when a fact was true in the world. A store can rank beautifully, carry immaculate valid_from and valid_to fields, and still lose an update.
Readers arriving from computer architecture will know the word from cache coherence between CPU cores. The analogy is worth exactly one sentence: several parties hold copies of one value, and something has to decide what happens when two of them change it. Everything after that sentence is different, so we will stay with the agent case.
What the protocol says, and what it deliberately does not
The Model Context Protocol standardises how a client discovers a tool, how it calls it, and what shape the answer takes. On the questions in the scenario above it is silent by design.
Revision 2026-07-28 made that silence louder by removing the session machinery that used to sit underneath. From the transport page:
Revision 2026-07-28 changed the behavior of Streamable HTTP. Clients must ensure they handle backwards compatibility correctly. Changes included: Removal of the GET stream endpoint. Removal of protocol-level sessions.
The versioning page states the shape that replaced it: "There is no negotiation handshake. Every request carries its protocol version, and the server accepts or rejects each request independently." Every message is its own HTTP POST, and one server "can handle multiple client connections". A server that supports only this revision, receiving legacy traffic, is told what to do with the old session machinery: an Mcp-Session-Id header on a request, "ignore it, and do not mint or echo session IDs"; a Last-Event-ID header, "ignore it; streams are not resumable".
Two things follow, and it is worth keeping them apart. The specification defines no memory-level ordering, conflict detection or merge semantics; that is the load-bearing fact. Session removal is the transport context around it, not its cause. A protocol with sessions would still have had to say something about concurrent writes to shared state, and this one does not.
What the spec does police is a narrower disagreement, and the reasoning is instructive. Servers "MUST reject requests where the values specified in the headers do not match the corresponding values in the request body", explicitly to prevent the case where "different components in the network rely on different sources of truth", such as a load balancer routing on a header while the server executes on the body. The same family of problem, one layer down, taken seriously. Above that layer, what your two editors believe about the deploy region is between them and the server.
Answer one: fail the write if the target moved
Anthropic's Managed Agents memory API gives a client a way to make the race visible. From Anthropic's memory documentation:
To avoid clobbering a concurrent write, pass a
content_sha256precondition. The update only applies if the stored content hash still matches the one you read; on mismatch, re-read the memory and retry against the fresh state.
That is compare-and-swap, in the shape any engineer will recognise:
read memory -> content, content_sha256
compute new content
update memory with precondition content_sha256
applied -> you had the current revision
mismatch -> somebody wrote first; re-read and decide againThe ergonomics are better than they look, because the hash does not cost a fetch. The listing endpoint returns content_sha256 on every item, "always populated so sync clients can diff without fetching content", specifically so that "sync clients can diff without fetching content". You can tell what moved before you decide what to read.
The precondition is optional, which is the part to notice. An update that omits it is not protected, and nothing in the API requires you to pass one.
Answer two: reject a correction whose target was claimed
Mnemoverse, our own memory API, arrives at a related outcome from a different direction, because its unit is not a file but a revision. A write can carry supersedes to mark itself as the correction for earlier atoms. From our reference:
All targets and the new atom are committed in a single transaction: if any target fails validation, the write rolls back entirely and none of the targets are touched. A target is not deleted; it gains a
superseded_bypointer to the new atom and stays fetchable by ID.
Two of the documented errors are exactly the concurrent-write case. If "a target already has a successor", the write fails with 409 CONFLICT naming that successor. And if "a target was claimed by a concurrent write between your read and this write", the answer is 409 CONFLICT: re-read that revision and supersede it instead.
These two mechanisms are not the same check. One compares a content hash on a file; the other refuses to let two corrections claim one predecessor. They prevent an overlapping class of race, by making the loser of it find out.
Our own limitation belongs in the same paragraph as our own primitive. Quoting our API reference rather than paraphrasing it:
The filter that hides a replaced revision from default reads now ships in the API, behind a server-side setting that is off in production today. While it is off,
POST /memory/read,/memory/recent,/memory/query, and batch fetches return superseded revisions exactly as before, sosupersedesdoes not yet make a stale fact stop showing up in search results. It records the correction; it does not yet hide the original.
So on our production surface, as of 2026-08-29 (the date our reference last checked that setting), a corrected fact is recorded as corrected and still comes back in a read. That is a coherence gap of a different kind: the store knows, and the default read does not act on it yet.
Answer three: nothing in particular, and you cannot tell
The third answer is the common one. A server that documents no guarantee and offers no precondition has made a choice the client cannot inspect. It might serialise writes internally, append both and reconcile later, or let the last writer overwrite the first.
The temptation is to call that last-write-wins. Resist it: the absence of a client-visible precondition does not prove what the server does. The honest client-side position is that you cannot tell, so you assume the weakest guarantee available and design a write path that survives a lost update.
Staleness is a product property, not a protocol one
Even without a conflict, two clients can simply be looking at different moments. Anthropic documents this precisely for one configuration. On a cloud sandbox the memory store is a live mount. On a self-hosted sandbox it is not: the SDK worker "reconciles each copy with its store after tool calls, at most once per sync interval (15 seconds by default), and once more when the session ends", so "another session running on a self-hosted sandbox sees a change only after both workers have synced".
Read that as what it is. Fifteen seconds is a default interval between reconciliations in one vendor's self-hosted worker, configurable, not a guaranteed visibility bound and not an MCP figure. It is quoted here because it is the rare case where a vendor states the shape of the window at all. If your own memory server does not tell you this, that is information too.
Shared memory is also a trust boundary
Concurrency and trust land on the same write path, which is easy to miss when you are only thinking about lost updates.
Anthropic's warning is blunt: if the agent processes untrusted input, a successful prompt injection "could write malicious content into the store", and "later sessions then read that content as trusted memory". The control offered is at the mount, not at read time: access "is enforced at the filesystem level: a read_only mount rejects writes", and a recommended shape is "one read-only store attached to many sessions (standards, conventions, domain knowledge), kept separate from each session's own read-write store".
So a shared store raises two questions per write, not one. Is this the current revision, and is this writer allowed to change it at all. Removing an unnecessary writer is often a cleaner coherence policy than reconciling one.
A write policy you can actually apply
None of this needs a distributed-systems project. It needs the write path to stop pretending it is alone.
- Separate corrections from additions. A write that claims to replace something needs a precondition. A write that only adds usually does not.
- For a correction, require a precondition: a content hash, a version, or a predecessor id, whatever your server offers.
- Treat the conflict as an expected outcome, not an error to swallow. A
409is the mechanism working. - Re-read before you retry, and decide again with the new content in hand. Retrying the same body against a moved target is how you lose the other writer's update politely.
- Ask your server what happens without a precondition. If the documentation does not say, assume the weakest guarantee.
- Know your staleness window, or find out that nobody has measured it.
- Reduce the number of writers. Read-only mounts for shared reference material remove whole classes of this problem instead of resolving them.
The protocol will not reconcile your two editors. Something has to, and by default it is you.
Common questions
Is there a memory coherence protocol in MCP? No. MCP specifies how a client discovers and calls a tool and what shape the answer takes. It defines no ordering rule between calls, no conflict detection and no merge semantics for what a memory server stores.
What happens if two agents write the same memory at the same time? That depends on the server, and from the client side you usually cannot tell. A server may serialise, append, reconcile, or overwrite. Without a documented guarantee or a precondition, assume the weakest outcome.
Did MCP remove sessions? Yes, in revision 2026-07-28, along with the GET stream endpoint. There is no handshake, every message is its own POST, and a server supporting only this revision is told to ignore an Mcp-Session-Id header rather than mint or echo one (a SHOULD in the spec).
How does Anthropic's memory API prevent a lost update? With an optional content_sha256 precondition: the update applies only if the stored hash still matches the one you read, and on a mismatch you re-read and retry against fresh state.
How long can a shared memory stay stale? It depends how the store is served. A cloud sandbox mounts it live; a self-hosted worker reconciles at most once per sync interval, 15 seconds by default, and again at session end. That is one vendor's documented default, not an MCP figure.
What should a client do before writing to a shared memory? For a correction, require a precondition so the write fails loudly if the target moved, treat the conflict as expected, and re-read before retrying. For a pure append, a precondition is usually unnecessary.
Related
- What is an MCP memory server? observes in one clause that the protocol defines "no ordering rule". This article is that clause expanded.
- Stateless MCP and agent memory asks where state lives under a stateless protocol. This asks what happens when two clients write to it.
- Memory API response shape is the read-side companion: a ranked list has nowhere to put a disagreement the store already knows about.
- Agent memory: evidence versus policy covers the neighbouring temporal question, closing a fact rather than replacing it.
- Anthropic's memory API has no ranked retrieval looks at the same vendor surface from the retrieval side.
Written by Edward Izgorodin. Specification and vendor pages quoted as of 2026-09-04.
