作者发现并行运行的多个AI Agent会意外修改彼此的文件,于是设计了一套「声明」机制——每个Agent在开始工作前先声明意图,完成后清理状态,避免覆盖他人成果。核心洞见是:Agent之间的「礼貌」不可靠,必须有可验证的协调机制。
我在本地并行运行多个 Agent 会话,每个会话各自在独立项目中运行,有段时间我以为这样是安全的,因为每个会话都有自己专属的仓库。但其实不是。会话访问自身目录树以外的情况比你想象的更频繁——这里是一个 memory 文件,那里是一份共享配置,在兄弟项目里顺手修了一个 bug,因为发现的问题恰好在别处浮出来了。
我坐下来审计记录,发现了五次冲突,它们的形态完全一致。第二个会话并非盲目行事,它看到了第一个会话正在工作的痕迹——状态文件里新写的 claim、一个写了一半的目录——但它还是继续执行了。Claim 当时是存在的,每个项目用自己的方式记录,用各自的状态文件,但没有任何东西会把它们读回来。我要明确一点:这不是模型粗心大意。在上下文压力下,Agent 会把另一个 Agent 的存在当作噪音来处理。
同样值得一提的是,我的 staging 规则在同一周也失效了,在我记录下来 67 分钟后再次被破坏,而那个会话从未亲历过那起事故。这个故事值得单独讲述,因为后来的修复方案与"写更好的文案"毫无相似之处。
于是 claim 从一种礼让变成了一种协议。不是什么新想法,而是一个标准化的想法:统一格式、统一存放地点、统一机械拒绝机制,而不是五个项目各自写笔记却没有人有义务遵守。在任何会话向自己的目录树以外写入之前,它要获取一个 claim——一个以目标命名的小型 JSON 文件,里面记录了谁、何时、以及要做什么。整个文件只有五个字段:
{
"target": "shared-config",
"holder": "session-a41",
"purpose": "align the lint rule with the new preset",
"taken_at": "2026-08-14T21:02:11Z",
"heartbeat_at": "2026-08-14T21:09:40Z"
}
围绕它的协议只有四个动词。在第一次外部写入前获取 claim,在每次写入操作时更新心跳,在结束时释放,如果另一个会话已经持有该 claim 且心跳尚新鲜,则回答 REFUSED,而 REFUSED 是一个答复,不是障碍。被拒绝的会话会排队一条交接笔记,工作稍后按顺序执行。
foreign write wanted
│
▼
take <target> ── held, heartbeat fresh? ──yes──▶ REFUSED
│ no, or stale │
▼ ▼
claim written, read back queue a handover,
│ the work runs later
▼
beat every writing turn ──▶ release at the end
我原本没料到会产生影响的那个部分,实际上却最为关键——验证 guard 本身。一开始,claim 脚本在写入 claim 文件悄然失败时也打印了"claimed"(目标名中的斜线破坏了路径)。一个在失败时报告成功的 guard 比没有 guard 更糟,因为会话会以为自己持有了一把不存在的锁,然后继续执行。修复方案很小——slugified 名称加检查写入——但教训更大。当你为 Agent 构建安全机制时,要用审视 Agent 同样的怀疑态度去测试这个机制。
一台健康的机器,在空闲时,claims 目录是空的。claim 超越其会话存活时间的,是我发现有问题写了一半时最先要看的东西。整个机制大约六十行 shell 代码。但价值从来不在代码本身。它出现在屏幕上那一刻——错误的操作变得响亮而非静默:
$ claim take shared-config "align the lint rule with the new preset"
REFUSED: shared-config is claimed by session-a41 (heartbeat 41s ago).