#AI#AI Agent#DevOps

多 Agent 共享记忆并发控制:版本提交、语义合并与冲突重放

多个 Agent 同时修改共享计划、偏好或结论时,简单最后写入会丢事实。本文用版本命令、单键排序和字段级合并策略控制并发。

本文要点

  • 主题聚焦:AI、AI Agent、DevOps
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2026年3月15日·更新于 2026年7月20日

研究 Agent 把结论写入共享黑板的同时,审校 Agent 可能撤销同一结论;两个执行 Agent 也可能基于版本 7 分别把计划改成不同状态。若存储层只是 UPDATE memory SET value = ?,后完成者会无声覆盖前者。多 Agent 通信设计 提到共享状态要串行化,本篇把它落实为可重放的记忆提交协议。

客户端提交命令,不提交整份快照

读取响应返回 memoryId、version 和字段版本。写入必须带 expectedVersion、稳定 commandId、基于哪个版本做出的 patch,以及 Agent 身份。HTTP 接口可用 ETag/If-Match 表达前置条件;内部队列也要保留同等语义,不能因为换了传输层就丢掉版本。

json
{
  "commandId": "run-82:step-4:memory-write",
  "memoryId": "task-19",
  "expectedVersion": 7,
  "op": "replace",
  "path": "/decision/deployRegion",
  "value": "ap-southeast-1",
  "evidenceIds": ["tool-551"],
  "actor": "planner"
}

重试复用同一 commandId。服务端对 (tenant_id, command_id) 唯一;同 ID 同 hash 返回原结果,同 ID 不同载荷标记协议冲突。随机生成新 ID 再重试会绕过去重,长任务 Agent 工作流 的 step identity 应贯穿这里。

用 CAS 让丢失更新变成显式冲突

事务先写命令去重记录,再以版本条件更新投影,成功后追加不可变事件和 outbox。受影响行数为零时返回当前版本及冲突路径,不自动读取后覆盖。

sql
UPDATE shared_memory
SET value_json = apply_checked_patch(value_json, $patch),
    version = version + 1
WHERE tenant_id = $tenant
  AND id = $id
  AND version = $expected;
-- row_count = 0 => PRECONDITION_FAILED

PostgreSQL 18 默认 Read Committed 下,普通读后写仍可能基于旧快照决策;版本谓词才是业务前置条件。热点键冲突高时,由按 (tenant,memoryId) 分区的单写入者排序命令,而不是无限提高事务隔离并让所有键互相等待。本文使用的 PostgreSQL 18 文档访问于 2026-07-18,目标版本变化时应重新跑隔离测试。

合并规则必须由字段语义决定

CAS 失败后,不要让模型看两份 JSON 后自由“综合”。不同字段采用显式代数:集合型标签按元素 ID 并集,只追加 evidence 按唯一 ID 合并;单值决策、权限、状态转换和用户偏好不能自动合并。计数器必须先区分 operation 与 state,不能把普通加法误称为幂等 merge。

text
/evidence/*          append_unique(evidenceId)
/labels/*            set_union(labelId)
/progress/completed  monotonic_true
/jobsStarted         increment_once(commandId, delta)
/tokenCountByActor   gcounter_join(max_per_actor)
/decision/*          reject_concurrent
/permissions/*       reject_concurrent
/status              validate_state_transition

increment_once 是 operation:同一 commandId 的 delta 只应用一次,两个不同 command 才相加;加法本身不满足 merge(a,a)=a。需要 state-based 合并时使用 G-Counter,每个 actor 只增加自己的分量,合并取各 actor 分量的 max,总数最后求和。只有 state join 才要求交换、结合和幂等;operation 则要求稳定 ID、去重和一次逻辑效果。乐观锁与悲观锁 解释数据库机制,这里的关键是字段语义。

用并发屏障与事件重放验收

测试让 100 个 worker 在同一 barrier 后基于版本 7 写同一单值字段,断言恰好一个提交、99 个显式冲突且没有事件丢失。对同一 increment command 重放十次应只增加一次,两个不同 command 应增加两次;G-Counter 的同一 actor 快照重复合并取 max 而非求和,不同排列得到同一状态。再注入事务提交后响应丢失、outbox 重投、worker 租约过期和进程崩溃,断言 command 只有一个逻辑效果。

从事件零开始独立重建投影,并比较版本、value hash、冲突集合和最后 sequence。只比最终 JSON 不够,因为被覆盖后碰巧相同也可能丢失审计。跨租户使用相同 memoryId 的压力测试必须永远互不可见。

上线先记录潜在冲突但沿用旧单写路径,量出热点键和合法重试率;再按租户打开 CAS。监控 precondition failure、自动合并、人工冲突、重试耗尽和重放差异。权限或决策字段发生一次错误自动合并就停止放量。

回滚将命令入口切回只读或旧单写者,保留新事件与 command 去重表,绝不能把 expectedVersion 校验关掉来“恢复成功率”。修复后从最后可信 sequence 重放。事件驱动架构 可承载投递,但共享记忆是否正确,最终取决于版本条件和字段语义是否可证明。

延伸阅读

Related

相关推荐