跳转至

协同文档下的 Agent 透明化编辑:可回滚、可对比的协作闭环

Ch04.381 协同文档下的 Agent 透明化编辑:可回滚、可对比的协作闭环

📊 Level ⭐⭐ | 9.1KB | entities/vivo-agent-transparent-collaborative-editing.md

协同文档下的 Agent 透明化编辑:可回滚、可对比的协作闭环

来源:vivo互联网技术(Ding Junjie)AI 编辑器二期调研实践 | 主题全库零覆盖

当 AI 以惊人速度编辑多人协同文档时,真正的难题不是"AI 能不能写得好",而是如何确保 AI 与人类精细化编辑和谐共存、不破坏协同信任感。这不仅是模型问题,更是协同架构挑战:关键在于明确 Agent 在协同系统中的"身份",并通过可回滚、可对比的机制把 AI 编辑纳入透明、可控的协作闭环。

Agent 身份三模式

Undo/Redo 是上世纪 70 年代的经典 UI 约定,但 Agent 踏入协同文档后失灵——改动不再只源于"我"。对比三种身份模式: - 独立 Peer:Agent 作为独立协作者接入——归属权清晰、撤销天然独立,但占用协同席位、权限和同步复杂度高。 - 附属 Cursor:Agent 绑定当前用户作辅助编辑者——权限简单、体验顺滑,但无额外标记则 AI 和人撤销流混在一起。 - 模拟用户:直接以用户身份修改——接入成本最低,但破坏信任、无法追溯自己 vs AI 改动。

最终取舍:倾向附属模式,但每次改动必须有独立"身份证明"(会话 ID/运行轮次/消息批次)。既不让 Agent 独立出现制造"虚拟同事",也不完全伪装成用户——系统才能区分用户输入、AI 编辑成果、AI 编辑后自动补齐的结构。

透明化编辑:事实链而非结果快照

Agent 修改大刀阔斧、跨结构重写。方案是可观测对比、一键撤回。工具入口层通过 editor.chain().changedByAI({ runId, sessionId, messageId }).run() 沿同一条"身份边界"处理。

  • 语义化差异对比:字符级 diff 对富文本是灾难(把一次段落重写拆成无数细碎增删),改用两层差异对比策略,用户看到"AI 在哪里动了刀"而非底层操作日志。
  • 事实链:在 ProseMirror 中 Step 是最小变更单元,序列化为 stepsJson。同一 runKey 只保存一份 baseJson,后续每个 message 只追加自己的 stepsJson——一轮 Agent 编辑变成 baseJson + ordered stepsJson 的可回放事实链。

三层心智模型(记录/展示/决策分离):记录层保存机器可还原的"事实";展示层把事实回放成 beforeDoc/afterDoc 并翻译成用户可读语义差异;决策层基于同一条"事实链"执行接受/拒绝/回滚。

run 级精准撤回

单人编辑器撤销是时间栈,但多人协同 + Agent 编辑场景不适用——时间上最后发生的操作不一定想撤销。典型场景:用户 A 触发 AI 改 2-3 段,同时人类用户在 4 段补一句话,用户 A 点"撤回 AI 修改",若全局撤回会误伤人类补充内容。

撤回语义拆为两类(按身份边界区分 AI 与人类编辑),撤回边界必须与差异对比边界一致。实现上,AI 编辑入口先把事务从普通编辑历史"摘出来"写入边界信息;事务进入 Yjs 侧后 UndoManager 利用同一组元数据过滤。

深度分析

身份三模式的本质是「信任坐标系」而非技术选型

独立 Peer、附属 Cursor、模拟用户三者的差异表面上是接入方式,实质是给"谁改了什么"划出一条可追溯的归属边界——这条边界决定了下游差异对比与撤销撤回能否成立。独立 Peer 归属权最清晰、撤销天然独立,代价是占用协同席位、抬高权限与同步复杂度;模拟用户接入成本最低,却彻底摧毁信任,用户无法区分自己与 AI 的改动;附属 Cursor 在权限与体验上最顺滑,但若不加标记,AI 与人类的撤销流会混在一起。作者的取舍不是三选一,而是在中间态上做加法:让 Agent 以附属身份共享当前用户上下文与权限,却为每一轮编辑打上独立"身份证明"(会话 ID/运行轮次/消息批次)。本质是既要"不制造虚拟同事",又要"不完全伪装成用户",把归属边界沉淀为可被后续机制消费的元数据。

事实链(baseJson + stepsJson)为何优于结果快照

结果快照保存的是一个状态,而非到达该状态的过程,因此既无法回答"AI 到底动了哪里",也无法回放或审计。事实链则反其道而行:以 ProseMirror 的最小变更单元 Step 为记录粒度,序列化为 stepsJson,同一次 run 只保留一份 baseJson,后续每个 message 只追加自己的 stepsJson。这种"一份基底 + 增量步骤"的结构带来三个工程红利——存储近乎 O(1) 基底加线性增量、回放顺序天然确定、差异对比与精准撤回共用同一条事实来源。它把"AI 改了什么"从一次性快照升级为可重放的审计资产,这正是透明化的数据基础。

撤回边界必须等于差异对比边界

单人编辑器撤销是时间栈,最近的操作最先被撤;但在多人协同加 Agent 编辑的场景里,时间先后不等于用户想撤销的先后。文中给出的例子极具代表性:用户 A 触发 AI 改写第 2-3 段,同时人类用户在第四段补了一句话,此时 A 点击"撤回 AI 修改",若走全局撤回,人类刚补充的内容会被无辜带走——这是协同文档绝对无法接受的。解法是把撤回语义按身份边界拆为两类,并强制一条设计原则:撤回边界与差异对比边界保持一致。实现上,AI 编辑入口先把自身事务从普通编辑历史中"摘出来"写入边界信息,事务进入 Yjs 侧后由 UndoManager 用同一组元数据过滤。对称性是这里的核心:凡是能展示出来的差异,就一定能被精准退回。

记录 / 展示 / 决策三层分离的心智模型

记录层保存机器可还原的事实,展示层把事实回放成 beforeDoc/afterDoc 并翻译成用户可读的语义差异,决策层基于同一条事实链执行接受/拒绝/回滚。三层各司其职、缺一不可,且共享同一事实来源,从而避免展示层语义改动反过来逼迫存储层重做。这个模型把"看得见、退得掉、不误伤协作者"拆解为可独立演进的工程职责,是整套透明化闭环的结构骨架。

实践启示

  1. 先定身份边界,再写编辑逻辑:任何 Agent 进协同文档前,先决定它作为独立 Peer、附属 Cursor 还是模拟用户,并为每轮编辑落一个独立标识(runId/sessionId/messageId)。身份边界是所有下游机制的地基,后补会非常痛苦。
  2. 用事实链而非结果快照做记录:以编辑器的最小变更单元(如 ProseMirror 的 Step)为粒度存步骤序列,同 run 只存一份基底,后续消息只追加增量。这既省存储,又天然支持回放、语义对比与审计。
  3. 让撤回边界严格等于差异对比边界:凡是能展示的差异都必须能退回,反之亦然。AI 事务要从普通编辑历史中"摘出来"并写入边界元数据,Yjs 侧 UndoManager 用同一组元数据过滤,避免"看得见却退不掉"。
  4. 不要在协同 + Agent 场景沿用时间栈撤销:最后发生的操作不一定是用户想撤销的。按身份边界把撤回语义拆成 AI 与人类两类,才能精准撤回 AI 改动而不误伤协作者的新增内容。
  5. 记录、展示、决策三层分离:分别对应机器可还原的事实、用户可读的语义差异、接受/拒绝/回滚的决策,让三者共享同一条事实链,使每一层都能独立演进而不互相牵连。
  6. 让自动补齐的结构也继承同一身份元数据:AI 编辑后编辑器插件追加的结构修正,必须带上与直接改动相同的 session/run/message 信息,否则会出现"主改动能撤、自动补齐撤不掉"的断层。

相关实体