Here is the failure versioning exists to stop.
A coding agent reads the team's note on session storage: "we store sessions in Redis". A second agent, working on the deploy, finds that sessions vanish on restart, moves them to Postgres, and updates the note: "we store sessions in Postgres, not Redis, so they survive a restart." Ten minutes later the first agent, still holding what it read, writes the note back with a small addition of its own. It is Redis again. Nobody did anything wrong, and the correct fact is gone.
Every shared store has this problem: a write based on a read that is already out of date. Databases have long answered it with optimistic locking: remember the version you read, and refuse the write if it changed [1]. With people, Git and code review catch it. Agents write in seconds and do not open pull requests, so the check has to happen on the write itself.
I.Every fact keeps its versions
In Willder, a fact is not a row that gets overwritten. Each write stores a new version, and the old one stays readable by its id, like a commit. Every answer to memory_query ends with the version it came from:
• we store sessions in Postgres, not Redis, so they survive a restart. (as of <when it was stored>) — from: <agent name> (agent) · version <version id>An agent that wants to replace that fact passes the id as baseVersion on its write. If that version is still current, the new one replaces it, the old one stops being served, and every other agent with access to the scope sees a supersede change naming the replacement [2].
II.A stale write is refused, and nothing is stored
If someone replaced the version after you read it, the write is refused. The refusal starts with stale_version: and names the newer version [3]:
stale_version: baseVersion <v1> does not match the current version of "session storage" in <scope>: the fact it is a version of is now <v2>. Nothing was stored. Read <v2> with memory_version, then write again with baseVersion <v2>.One memory_version call on that id says who wrote it and when (written by <actor> at <time>), whether it is current, and what it replaced. The agent reads it, decides, and writes again. Figure 1 shows the whole loop, and it is what stops the first agent in my example from putting Redis back.
Agents A and B both read version v1 of a fact. B writes with baseVersion v1 and its write is stored as v2. A then writes with baseVersion v1; Willder refuses with stale_version naming v2 and stores nothing. A reads v2 with memory_version, then writes with baseVersion v2, which is stored as v3.
Most clients never send baseVersion, so the server does not depend on it. It keeps its own record of every version it served to each agent. A write without a base, about a fact that has a newer version this agent was never shown, is refused too, with the same stale_version: prefix [4]. The agent does not have to track anything.
III.When two writes land at the same moment
Two agents can also write the same fact within the same second, neither having seen the other. Willder does not pretend to know which one is right. The version stored first stays current, the other is kept as superseded and stays readable, and its writer is told on its next tool call [5].
IV.Starting a session with what changed
Refusing bad writes is half of it. The other half is making sure an agent starts with what it needs, so it does not make the bad write in the first place.
On the first tool result of a session, Willder attaches a task-start brief from the agent's scope: the rules in force, the decisions that are settled, recent writes, and what is still open. It has a budget of an estimated 600 tokens, and is at most 694 [7]. If the agent names its task, for example a ticket key or a branch name, the brief leads with what earlier sessions of that task decided and wrote. In Claude Code that is one header in .mcp.json [8]:
{
"mcpServers": {
"willder": {
"type": "http",
"url": "https://willder.ai/api/mcp",
"headers": { "Authorization": "Bearer ${WILLDER_TOKEN}", "X-Willder-Task": "${WILLDER_TASK:-}" }
}
}
}When an agent comes back after a break, the second line of the brief says what happened while it was away:
Since your last session (last call 2026-10-29 14:05 UTC): 1 change by 1 other writer in your scope.The count covers only changes by others, inside the agent's scope, that it has not already been told about [9]. It carries no fact text, so it cannot leak anything the agent may not read.
After that, every tool result carries a short change notice (unless the client asks for compact replies and reads the feed itself): what other agents and people changed in the scope since the agent's last call. A burst of changes is merged into fewer lines, never dropped, and the last line says how many are still waiting. If reading the feed fails, the tool's own result comes back untouched [10].
V.The brief has to be fast
A brief that rides on every session's first call is a tax on every session, so I set a bar: it may add at most 200 ms to that call, at the 95th percentile.
The server reports how long the brief took on each call. A fresh session's first call builds the brief; its second call pays only the session check every call pays. So for session the brief added
where is the -th smallest of the differences.1 Each run takes sessions, one call at a time, so its p95 is the 38th smallest: two slow sessions out of forty can sit above it, three cannot.
Early on 29 October 2026 (UTC) I measured it on production, on a scope holding a team's real work. It added a p95 of 268.6 ms (median 201.5 ms), and 210.3 ms (median 195.8 ms) on a repeat run. Over the bar both times.
I profiled it on a local Postgres seeded to look like that scope. The profile showed two problems, both in one database read. First, for an agent's first session, and for any session that was not a return after a break, the "since your last session" count walked the organization's event log to count nothing. On the test data it read 369 events; it now reads none unless the session really is a return. Second, three index walks checked each row's scope by casting its path to ltree. A plain text prefix test, Postgres's starts-with operator [11], now runs beside the ltree test [12], and the ltree test still decides every row:
col ^@ $a AND col::ltree <@ $b::ltreeNo new index, no migration, no cache, and the access check and its audit row were not touched.
Later the same day, on the deployed fix, the same measure on the same scope gave a p95 of 88.0 ms (median 65.4 ms), and 66.0 ms (median 59.0 ms) on a repeat run (Figure 2). That is
times lower at the 95th percentile, and under the bar by a margin I am comfortable with. On a small demo scope the brief went from 145.8 ms to 31.8 ms.
VI.How fast a write reaches the other agents
The other number I watch is write-to-read: from the moment one agent sends a write to the moment another agent can read it. The same day, on production:
| Load | Samples | Median | p95 |
|---|---|---|---|
| One writer at a time | 60 | 538 ms | 881 ms |
| Six writers at once | 60 | 688 ms | 1,292 ms |
| Every write retried, one writer revoked halfway | 66 | 834 ms | 1,588 ms |
Every write became visible, and the revoked writer's next write was refused. What the measure did not cover: a reader with a backlog of unread changes (every reader caught up on its feed before the run started), a burst of 403 refusals, a restart of the service, and retries sent without an idempotency key.
VII.What this does not do
It does not merge. It does not stop two agents from both being wrong. And it only helps agents that talk to Willder: an agent that keeps its own copy of a fact in a local file will not hear about a newer version until it reads again.
What it does do is make sure no agent silently undoes another's work, and that every agent starts a session knowing what changed while it was away. That is the part a folder of notes, or a search index on its own, does not give you.
Notes
-
This is the nearest-rank definition: no interpolation between samples, so the p95 reported is always one of the measured values. ↩