Agent 并发写同一份记忆会静默丢更新,Omnigraph 用 Git 分支模型接管冲突
两个 Agent 同时对同一份共享记忆做读-改-写,丢更新会静默发生:Codex 拿着旧副本回写整条记录,把 Claude Code 刚改成 Postgres 的字段又改回 Redis,两边日志都显示成功。作者梳理了三种常见解法——单一写入者、行锁/乐观版本校验、last-write-wins——并指出共同缺口:一次多步任务跨多条记录,这些机制既不产出可审阅的 diff,也不留历史。文章随后介绍 Omnigraph(Rust 编写、MIT 许可的开源图数据库,内置 Git 模型):每个 Agent 写自己的分支,merge 时与 merge base 比对,main 被改过就返回 divergent_update 并拒绝发布。动手部分用 Claude Code 与 Codex 跑完整流程,覆盖 @key 稳定 ID、--snapshot 读旧版本、Cedar 策略加独立 token 让写 main 返回 403。适合在搭多 Agent 共享状态的人;标题声称的 1000 Agent 规模并未演示,实测只有一名开发者加两个 Agent。


Cognition wrote in April that multi-agent systems work best when "writes stay single-threaded."
This implies that while many agents can think in parallel, only one of them should save to shared memory.
Cognition 在 4 月写道,多 agent 系统效果最好的时候,是“写入保持单线程”。
这意味着,许多 agent 可以并行思考,但只有一个应该把结果保存到共享记忆。
Shared writes are risky for a concrete database reason, known as the lost update, in which two writers save the same record and the second save silently erases the first, with no error on either side.
共享写入有风险,原因是一个具体的数据库问题:丢失更新。两个写入者保存同一条记录,第二次保存会静默抹掉第一次的结果,双方都不会报错。


For a developer running Claude Code and Codex in parallel on shared memory, this turns parallelism into a liability, because the more those agents write at once, the more of their saved work can potentially disappear.
对同时运行 Claude Code 和 Codex、让它们共用记忆的开发者来说,并行反而成了负担:两个 agent 同时写得越多,保存下来的工作就可能消失得越多。
Today, let's understand why this happens, the typical solutions and their problems, and how Omnigraph (open-source), a graph database with the Git model built in, solves it, followed by a hands-on walkthrough with Claude Code and Codex.
今天,我们先弄清为什么会发生这种事、常见方案各自有什么问题,以及内置 Git 模型的开源图数据库 Omnigraph 如何解决它;最后再用 Claude Code 和 Codex 做一次上手实测。


Agent memory updates are usually read-modify-write cycles, where an agent loads a record into its context window and later writes back whatever version the model produced.
Agent 的记忆更新通常是一个读-改-写循环:agent 把一条记录读进上下文窗口,过一会儿再把模型生成的版本写回去。
So consider a shared project graph where Claude Code and Codex read the sessions component at the same moment, when it says Redis and has no owner.
设想一个共享的项目图谱:Claude Code 和 Codex 在同一时刻读取 sessions 组件,当时它写着 Redis,而且没有 owner。
Claude Code finishes first and saves the store as Postgres, while Codex, still working from its earlier read, sets the owner and saves its own copy of the record.
Claude Code 先完成,把存储改成 Postgres;Codex 还在基于更早的读取结果工作,它设置 owner,然后保存自己手里的那份记录。


Codex's copy still says Redis, so its save puts sessions back on Redis and erases Claude Code's update. Both agents report success, and nothing in the logs shows that one of them was overwritten.
Codex 那份记录里仍然写着 Redis,所以它的保存把 sessions 又改回 Redis,抹掉了 Claude Code 的更新。两个 agent 都报告成功,日志里也看不出其中一次更新被覆盖了。
Agents make this worse than human users do.
A person editing a record usually changes one field and saves only that field, but an agent typically writes back the entire record it read earlier, so any field another agent updated in the meantime gets reset to the old value.
Agent 会让这个问题比人类用户更严重。
人编辑一条记录时通常只改一个字段,也只保存那个字段;agent 却往往把之前读到的整条记录写回去,于是另一个 agent 期间更新过的字段会被重置成旧值。
There are three common ways to fix this, and each has some trade-offs:
有三种常见办法可以解决,但每一种都有取舍:
- Single writer → one agent owns every write and the others send it suggestions, which keeps memory correct but caps the whole system at the speed of that one agent.
- 单写者 → 由一个 agent 负责所有写入,其他 agent 把建议发给它。这样记忆能保持正确,但整个系统的速度被这一个 agent 卡住。


- Locks and optimistic version checks → a conflict only fires when two writers touch the same record, so a version check would have rejected Codex's stale save in the above case. Both still guard one record at a time, though, while an agent's task usually spans several records and several steps, and neither leaves a diff to review or a history to audit.
- 锁和乐观版本检查 → 只有两个写入者碰到同一条记录时才会触发冲突,所以在上面那个例子里,版本检查本可以拒绝 Codex 的过期保存。但它们仍然一次只保护一条记录,而 agent 的任务通常横跨多条记录、多个步骤;两者也不会留下可审阅的 diff 或可审计的历史。


- Last write wins → every agent saves freely, and the latest save overwrites the rest, which keeps everyone fast and is exactly how Claude Code's update disappeared in the above example.
- 最后写入获胜 → 每个 agent 都可以自由保存,最新一次保存覆盖其余结果。这样所有人都很快,也正是上面例子里 Claude Code 的更新消失的方式。


Collectively, these fixes also miss isolation.
An agent working through a multi-step task needs its own workspace, a diff of what it changed, a review before that change is pushed, and a record of who changed what.
合在一起看,这些修复方案还都缺少隔离。
一个 agent 执行多步任务时,需要自己的工作区、能看到自己改了什么 diff、在推送前经过审阅,还要有一份记录说明谁改了什么。
Code faced the same problem when many engineers edited one codebase, and version control solved it without making anyone wait.
当年许多工程师编辑同一个代码库时,代码也面临同样的问题,版本控制解决了它,而且没有让任何人排队等待。
In Git, each engineer works on an isolated branch, a diff shows exactly what changed, and a merge compares both sides against the commit they started from, called the merge base, so a change built on an outdated version gets caught before it lands, while the history keeps every step.
在 Git 里,每个工程师在自己的隔离分支上工作,diff 精确显示改了什么;合并时会把两边都和它们共同的起点提交做比较,这个起点叫 merge base。这样,建立在过时版本上的改动会在落地前被抓住,同时历史记录保留每一步。
Agent memory needs that same versioning model, and that model is implemented by Omnigraph.
Agent 记忆也需要同样的版本模型,而 Omnigraph 正是实现了这个模型。
Omnigraph is an open-source graph database, written in Rust under the MIT license, with the Git model built in, so agent memory gets branches, diffs, merges, and history.
Omnigraph 是一个开源图数据库,用 Rust 编写,采用 MIT 许可证,内置 Git 模型,因此 agent 记忆天然拥有分支、diff、合并和历史。
Each agent writes to its own branch, so no agent waits on another, and nothing reaches main until a merge compares that branch with its merge base.
每个 agent 写入自己的分支,所以谁都不用等别人;在合并把该分支和它的 merge base 做比较之前,任何东西都不会进入 main。
If main has changed the same record since the branch was created, the merge refuses the write, publishes nothing, and tells the agent to refresh and try again.
如果分支创建之后 main 改过同一条记录,合并就会拒绝这次写入,不发布任何内容,并让 agent 刷新后重试。
The binary also ships an agent skill, so Claude Code and Codex can drive branches and merges from plain-English prompts.
这个二进制程序还自带一个 agent skill,因此 Claude Code 和 Codex 可以用自然语言提示来驱动分支和合并。
To test it, we started both agents together on two tasks that touch the same record. The recording below is the real run, with help-page lookups trimmed:
为了测试,我们让两个 agent 同时开始执行两个会碰到同一条记录的任务。下面的录屏是真实运行,只剪掉了查帮助页面的部分:
The CLI installs with one script, and skill export writes the skill where Claude Code and Codex look for it:
curl -fsSL https://raw.githubusercontent.com/ModernRelay/omnigraph/main/scripts/install.sh | bash
omnigraph skill export ~/.claude/skills/omnigraph
omnigraph skill export ~/.agents/skills/omnigraph
CLI 用一个脚本就能安装,skill export 会把 skill 写到 Claude Code 和 Codex 查找的位置:
curl -fsSL https://raw.githubusercontent.com/ModernRelay/omnigraph/main/scripts/install.sh | bash
omnigraph skill export ~/.claude/skills/omnigraph
omnigraph skill export ~/.agents/skills/omnigraph


To demonstrate the usage of Omnigraph, we created shared data, which is a small project graph seeded with two components, sessions and billing.
Since it has Omnigraph skill, Claude Code wrote its schema and seed from one prompt, then created the graph on main, as shown below:
为了演示 Omnigraph 的用法,我们创建了一份共享数据:一个小型项目图谱,种子里有两个组件 sessions 和 billing。
由于已经装了 Omnigraph skill,Claude Code 用一条提示就写好了 schema 和种子数据,然后在 main 上创建了图谱,如下所示:


The only detail worth knowing is @key on each slug, which gives every record a stable ID that merges compare across branches:
唯一值得了解的细节是每个 slug 上的 @key,它给每条记录一个稳定 ID,让合并在不同分支之间做比较:


Here, we gave Claude Code a multi-step task.
We asked Claude Code to record that sessions moved from Redis to Postgres.
To do that, it added a new decision, linked it to the component and the open issue, marked the old decision as superseded, changed the store to Postgres, and closed the issue. All six writes landed as one commit on its own branch, claude, as shown below:
这里,我们给 Claude Code 一个多步任务。
我们让它记录 sessions 已从 Redis 迁移到 Postgres。
为此,它新增了一条 decision,把它关联到组件和未关闭的 issue,把旧 decision 标记为已取代,将存储改为 Postgres,并关闭 issue。六次写入全部作为一个提交落在它自己的分支 claude 上,如下所示:


At the same time, we asked Codex to set the owner of sessions to platform-team. This was a one-field change, and Codex made it on its own branch, codex, as shown below:
与此同时,我们让 Codex 把 sessions 的 owner 设为 platform-team。这是一个单字段改动,Codex 在它自己的分支 codex 上完成,如下所示:


Both branches started from the same commit of main, so Codex's branch still reads store: redis.
两个分支都从 main 的同一个提交开始,所以 Codex 的分支里仍然写着 store: redis。
Before Claude Code's branch goes into main, we can review exactly what it changed.
Omnigraph compares the branch with the commit both branches started from, so the diff shows only Claude Code's six writes and nothing else, as shown below:
在 Claude Code 的分支进入 main 之前,我们可以精确审阅它改了什么。
Omnigraph 会把该分支和两个分支共同的起点提交做比较,所以 diff 只显示 Claude Code 的六次写入,别的什么都不显示,如下所示:


Since nobody had changed main after the branch was created, the merge is a fast-forward. That means main simply moves up to Claude Code's commit, so all six writes are pushed together.
由于分支创建后没人改过 main,这次合并是 fast-forward。也就是说,main 直接前进到 Claude Code 的提交,六次写入一起被推上去。
This is the part a regular database doesn't give you. A row lock protects one record at a time, but it can't show you a multi-step change as a single diff or let you approve it before it goes live.
这正是普通数据库给不了的部分。行锁一次只保护一条记录,但它无法把多步改动呈现为一个 diff,也无法让你在改动上线前批准它。
Next, we tried to merge Codex's branch. Its diff shows only the owner change, but the merge still fails, as shown below:
接下来,我们尝试合并 Codex 的分支。它的 diff 只显示 owner 改动,但合并仍然失败,如下所示:


The merge fails because both main and the codex branch changed sessions after the two branches were created. Omnigraph reports this as a divergent_update, publishes nothing, and tells Codex to refresh its branch, as shown below:
合并失败,是因为两个分支创建之后,main 和 codex 分支都改过 sessions。Omnigraph 把这种情况报告为 divergent_update,不发布任何内容,并让 Codex 刷新自己的分支,如下所示:


With no version check, a full-record save from Codex would have put sessions back on Redis, even though decision dec-12 says it moved to Postgres.
如果没有版本检查,Codex 的全量保存会把 sessions 改回 Redis,尽管 decision dec-12 已经说明它迁移到了 Postgres。
Omnigraph checks conflicts per record, not per field, so it refused this merge even though the two agents changed different fields. A false alarm costs one retry, while a stale merge that goes through quietly leaves a wrong fact in memory.
Omnigraph 按记录检查冲突,而不是按字段,所以即使两个 agent 改的是不同字段,它也会拒绝这次合并。误报的代价是重试一次;而过期合并如果悄悄通过,就会把错误事实留在记忆里。
Next, we sent the merge result back to the same Codex session. Codex read the latest main, opened a new branch called codex-2, and made its change again, as shown below:
接着,我们把合并结果发回同一个 Codex 会话。Codex 读取最新的 main,开了一个叫 codex-2 的新分支,然后重新做它的改动,如下所示:


This time, the merge is a fast-forward, and main keeps both agents' work. Old versions also stay readable. A query with --snapshot set to the original commit still returns Redis and no owner, as shown below:
这一次,合并是 fast-forward,main 保留了两个 agent 的工作。旧版本也仍然可读。把 --snapshot 设为原始提交再查询,返回的仍然是 Redis 且没有 owner,如下所示:


Up to here, an agent could still merge its own branch into main without anyone checking it. Locally, nothing stops that. The --as flag only names who made a change, and any agent can type any name.
到这里为止,agent 仍然可以在没人检查的情况下,把自己的分支合并进 main。在本地,没有任何东西能阻止这件事。--as 标志只是说明改动是谁做的,而任何 agent 都可以随便填一个名字。
To lock main, we ran Omnigraph as a server, omnigraph-server, and gave each agent and the reviewer their own access token. With tokens in place, the server knows who is making each request.
为了锁住 main,我们以服务端模式运行 Omnigraph,也就是 omnigraph-server,并给每个 agent 和 reviewer 分配各自的访问 token。token 到位后,服务端就能知道每个请求是谁发的。
Next, we added a policy. Omnigraph writes its policies in Cedar, an open-source language for writing access rules. Our rule says that agents can write on their own branches, and only the reviewer can merge into main.
接下来,我们加了一条策略。Omnigraph 用 Cedar 编写策略,这是一种用于写访问规则的开源语言。我们的规则是:agent 可以在自己的分支上写入,但只有 reviewer 能合并进 main。
Then we tested it. Claude Code's token could still write on its branch, but its attempts to write to main or merge into it came back as HTTP 403, which means access denied, as shown below:
然后我们做了测试。Claude Code 的 token 仍然能在自己的分支上写入,但它尝试写 main 或合并进 main 时,返回的是 HTTP 403,也就是拒绝访问,如下所示:


The reviewer's token then merged the branch. So agents still work in parallel, but nothing reaches main until a person approves it.
随后,reviewer 的 token 合并了这个分支。于是 agent 仍然可以并行工作,但在人批准之前,任何东西都进不了 main。
What we ran here was local, with one developer and two agents.
The same race happens at a much larger scale. Picture a company where many people and their agents read and write one shared database, such as a support bot, a coding agent, and an analyst's agent all updating the same customer records.
我们这里跑的是本地环境,一个开发者、两个 agent。
同样的竞争会在更大规模上发生。设想一家公司,许多人和他们的 agent 读写同一个共享数据库,比如客服 bot、编码 agent 和分析师的 agent 都在更新同一批客户记录。
There, one stale write can spread wrong facts into every tool that reads that data.
Omnigraph's model holds up at that scale too. Every person or agent works on its own branch, each change shows up as a diff someone can review, and only merges checked against the latest main go live.
在那里,一次过期写入就可能把错误事实扩散到每个读取该数据的工具。
Omnigraph 的模型在那种规模下也成立。每个人或 agent 都在自己的分支上工作,每个改动都以 diff 的形式出现、可供审阅,只有对照最新 main 检查过的合并才会生效。
You can find the Omnigraph GitHub repo here →
(don't forget to star it ⭐)
你可以在这里找到 Omnigraph 的 GitHub 仓库 →
(别忘了给它点星 ⭐)
👉 Over to you: How do your agents handle two writes to the same record today?
👉 轮到你了:今天你的 agent 如何处理对同一条记录的两次写入?
That's a wrap!
If you enjoyed this tutorial:
Find me → @_avichawla
Every day, I share tutorials and insights on DS, ML, LLMs, and RAGs.
就到这里!
如果你喜欢这篇教程:
找到我 → @_avichawla
我每天分享关于 DS、ML、LLM 和 RAG 的教程与见解。