对话分支作为一等公民:为什么线性线程迫使用户不断杀死并重启对话
你的聊天产品需要分支功能的最明显信号,往往也是最容易被忽略的:用户不断地将旧对话复制粘贴到新的会话中。他们不是在更换供应商,也不是因为无聊。他们是在尝试询问“如果我早点反驳那个假设会怎样?”,同时又不想丢失花费了 40 轮对话建立的上下文。线性线程只为他们提供了两个选项——要么覆盖下一条消息并丢失原始回复,要么开始新聊天并丢失前置内容。于是,他们利用剪贴板发明了第三种方案。
每当用户这样做时,你的产品就在通过这种变通方法泄露一个功能需求。这个变通方法很糟糕:它剥离了消息元数据,破坏了工具调用(tool-call)的联动,丢失了文件附件,并产生了不再映射到连贯任务的孤立线程。但它之所以存在,是因为另一种选择——放弃花费 30 分钟建立的上下文——更糟糕。对话在结构上是一棵树,而 UI 却坚持它是一个列表。用户只能手动填补这个鸿沟。
将分支作为一等公民,意味着像版本控制系统处理分支那样对待发散 :将其视为一种保存历史、支持并行探索并允许合并回来的正常操作。OpenAI 在 2025 年末在 ChatGPT 中以“在新聊天中分支”的形式发布了此功能。Claude Code 将对话存储为消息的 DAG(有向无环图),其中编辑会创建分支而非覆盖。LangGraph 检查点将 fork_at(checkpoint_id) 作为原语暴露。这种模式正在趋同,因为线性线程的抽象一直是有损的——只是经过几年的使用数据,这种损失才变得不可否认。
线性线程迫使的三种失败模式
线性聊天 UI 将三种截然不同的用户意图压缩到了同一个 UI 动作中。当用户想要改变方向时,他们会编辑最后一条消息。但“改变方向”隐藏了至少三种不同的需求,每种需求都值得一个不同的状态转换。
第一种是纠偏(course correction):用户认为模型理解有误并想要重新陈述。原来的回复不再需要;覆盖是可以的。第二种是替代探索(alternative exploration):用户得到了一个合理的答案,但想看看不同的表述会产生什么结果——两者都有价值。第三种是回滚到分支点(rollback to a fork point):用户意识到十轮对话前他们应该给出不同的约束,现在想在这些约束下重试后续的整个对话,同时保留原始分支作为参考。
在线性线程中,这三者看起来完全一样:用户点击消息上的编辑并重写它。系统无法区分“丢弃剩余部分”和“两者都保留”。大多数产品默认选择丢弃,因为保留会产生线性 UI 无法表示的导航问题。想 要“两者都保留”语义的用户如果不离开产品就无法实现。
这种成本表现为重复劳动。进行对比分析的研究人员会打开同一个聊天的四个浏览器标签页。测试语气变化的营销人员会开启新的会话,并重新将简报粘贴到每个会话中。调试多步骤计划的工程师要求模型“回到第三步”,然后眼睁睁地看着模型编造之前的上下文,因为模型引用的对话已经被更改了。分支模式从底层涌现——虽然笨拙、摩擦力大,且模型无法提供帮助,因为每个分支都存在于不同的会话中。
分支时复制(Copy-on-Branch)是正确的状态模型
大多数团队在第一次尝试时犯的错误是将分支视为对话的深拷贝。在用户开始频繁使用分支之前,这还能应付,但随后存储成本和更新语义就会成为问题。一个拥有 60 轮对话的用户如果在第 50 条消息处创建了 5 个分支,不应该为 300 条消息的存储付费,也不应该在共享前缀之间出现任何不一致的情况。
正确的模型是带有结构共享的分支时复制(copy-on-branch with structural sharing):消息是不可变的,分支是指向 DAG 的指针,共享前缀在磁盘上只存在一份。这正是使 Git 具备可扩展性的核心洞察。分支不是树的副本;它是指向某个提交(commit)的新引用(ref),而提交是追加式图中的内容寻址节点。转化为聊天场景:每条消息都是一个带有父级指针的节点,分支是叶子引用,用户看到的“对话”是在读取时从根节点到叶子节点重建的一条路径。
这使得几个原本昂贵的操作变得廉价。分叉(Forking)的时间复杂度是 O(1)——你只需分配一个指向分叉点消息的新叶子引用。切换分支只是指针更改,而非副本制作。对比两个分支变成了路径之间的树差异对比(tree-diff),这对于向用户展示“对话是在这里分叉的”非常有用。垃圾回收变成了可达性分析:没有叶子引用指向的消息是可以删除的,但绝不能静默删除——它们是某人的历史。
- https://medium.com/@nikivergis/ai-chat-tools-dont-match-how-we-actually-think-exploring-the-ux-of-branching-conversations-259107496afb
- https://piebald.ai/blog/messages-as-commits-claude-codes-git-like-dag-of-conversations
- https://docs.langchain.com/oss/python/langgraph/persistence
- https://arxiv.org/abs/2512.13914
- https://arxiv.org/html/2603.21278
- https://github.com/ishandhanani/forky
- https://github.com/DrustZ/GitChat
- https://ericmjl.github.io/blog/2025/12/31/canvas-chat-a-visual-interface-for-thinking-with-llms/
- https://docs.langchain.com/oss/python/langchain/frontend/branching-chat
- https://tldraw.dev/starter-kits/branching-chat
- https://help.openai.com/en/articles/6825453-chatgpt-release-notes
- https://knowledge.buka.sh/the-hidden-fork-how-editing-messages-in-chatgpt-lets-you-branch-conversations/
