给前端 Agent 加协同编辑时,我会先把 CRDT 文档和 AI 建议账本拆开
最近在做一个带协同编辑能力的前端 Agent 工作台时,我遇到一个很具体的问题:多人同时改同一份需求文档,AI 又会根据上下文生成补全、改写和检查建议。第一版把模型返回直接写进编辑器内容,看起来省事,联调时却很快乱起来。A 同学还在改段落,B 同学已经看到 AI 改过的句子,离线恢复又把旧建议重新应用了一次,最后谁确认过、谁只是看到建议,界面说不清。

原创示意图:把已确认正文、协同临时状态、AI 建议、人工确认和本地持久化拆成可恢复的协作链路。 来源:Codex image generation
问题背景
协同编辑本身有成熟工具可以承接。Yjs 的文档围绕 shared types 组织协同数据,常见对象包括 Y.Text、Y.Map 和 Y.Array;document updates 文档把更新传播描述成可编码的增量。y-websocket 提供 WebSocket provider,并支持 awareness 这类临时协作状态;y-indexeddb 可以把 Yjs 文档持久化到浏览器 IndexedDB,用来改善离线编辑体验。MDN 的 WebSocket API 文档给出了浏览器和服务器之间双向通信的基础边界。
这些能力能解决多人编辑同步,却不会自动解决 AI 建议的产品语义。模型生成的一段 patch 还没有经过人工确认,它只是候选动作。如果直接写进 CRDT 文档,协作者都会把它看成已发生事实,撤销、复核和审计都会变得含糊。
踩坑和关键难点
第一个坑是建议和正文混写。AI 改写可能基于几秒前的选区,用户已经继续输入后,旧建议再落到共享文档,就会挤掉新的人工修改。第二个坑是 presence 太重。光标、选区、正在思考、建议预览适合走 awareness 或本地状态,强行持久化会制造大量无意义历史。第三个坑是离线恢复。CRDT 更新可以帮助文档合并,但 AI 建议还带着 prompt、模型输出、人工确认和失败原因,必须能单独追踪。
解决思路
我把链路拆成四层。第一层是 shared document,只保存所有人已经确认的正文结构。第二层是 suggestion ledger,保存 AI 产生的候选 patch,字段包括 suggestionId、baseDocHash、selectionRange、promptHash、patchOps、status 和 reviewerId。第三层是 awareness channel,只广播光标、选区、在线用户和当前预览建议。第四层是 apply gate,负责把人工确认后的建议转成一次明确的 CRDT transaction,并把 transaction id 回写到账本。
这样处理后,AI 没有权限直接修改共享正文。它只能写建议账本,界面把建议以高亮、旁注或对比视图呈现给用户。用户确认后,应用层先检查 baseDocHash 和当前文档是否还能对齐;能对齐就应用 patch,不能对齐就进入冲突复核,要求用户重新选择范围或让 Agent 基于最新内容再生成一次。
关键步骤
第一步,在调用模型前冻结上下文。把当前选区、周边段落、文档版本摘要和协作者状态写进 request snapshot,排查时才能知道 AI 当时看到了什么。
第二步,让模型输出结构化 patch。不要只收一段最终文本,我会要求它返回 insert、delete、replace 这类操作,以及每个操作的理由和目标范围。前端收到后先做 schema 校验,再写入 suggestion ledger。
第三步,应用前做冲突检查。如果目标段落已经被其他人改过,就不要静默合并。可以尝试重新定位 anchor,也可以进入 review 状态,但每一步都要留下事件。
第四步,把离线恢复和协同同步分开。Yjs 文档通过 provider 和本地持久化恢复正文,建议账本通过业务 API 或 IndexedDB 恢复待审核项。页面启动时先恢复正文,再恢复建议列表,避免把过期建议提前渲染成可应用状态。
可复用经验
前端 Agent 进入协同编辑场景后,要先分清三类事实:已经确认的正文、正在协作的临时状态、等待人工确认的 AI 建议。CRDT 负责第一类,awareness 负责第二类,建议账本负责第三类。边界清楚后,撤销、审计、离线恢复和多人复核都会更稳定。
我现在会把验收清单固定成四问:AI 是否只能写建议账本,建议应用前是否检查版本,光标和预览状态是否避免落入正文历史,刷新后是否能解释每一条建议的来源和处理结果。四问都能回答,协同 Agent 才算进入可维护状态。