Fork Agent:共享上下文的艺术

Sub Agent:上下文管理的艺术 里讲了一种「分身」做法:新开一个空白的 messages 数组,让子 agent 在隔离的上下文里跑探索性任务,做完只把结论带回主线。

但有些任务的性质正好相反:它必须看到主对话的完整历史才能干活。

比如从当前讨论的某个节点尝试另一种方案,让 agent 帮你总结一下当前会话,或者在不打断主对话的前提下回答一个临时小问题。这些任务都强依赖已有对话历史,新开空白上下文根本无从下手。

Codex 和 Claude Code 的底层思路是类似的:从当前对话 fork 一份已有历史,在末尾追加一条新的任务指令,后续产生的消息放在独立分支里,不写回主对话。

两个工具暴露给用户的入口略有不同。Claude Code 提供 /btw 临时提问;Codex 对应的是侧边栏临时会话,在 Codex CLI 中可以用 /side 打开。Codex 还提供 /fork,把当前会话复制成一条可以长期继续的新会话分支。

本文主要以 Claude Code 为例,因为它围绕 Prompt Cache 复用和工具权限控制的工程细节很有代表性。Codex 的具体内部实现可能不同,但从上下文组织方式来看,都是基于已有对话创建一条互不干扰的新分支。

听起来很简单,不就是「新开一个 agent,把历史带过去」嘛?机制本身确实就这么简单。

但要把这件事做好得费点工夫,关键是 Claude Code 怎么在尽可能复用Prompt Cache 的同时,还能防止子 agent 乱改你的项目文件。

下面会看到,权限管控的具体做法其实是被 cache 共享的约束逼出来的,这两件事是连在一起的。

Fork Agent 的本质

下面以 Claude Code 的实现为例。fork 时实际发生的事情很简单:

复制代码
父对话 messages = [m_1, m_2, ..., m_n]

fork 子 agent messages =
  [m_1, m_2, ..., m_n,        // 完整复制父对话
   user("新任务指令")]         // 末尾追加一条 user 消息

子 agent 直接拿这份历史当起点,末尾再追加一条新的 user 消息给具体指令。这条 user 消息是唯一因子 agent 而变化的内容。

fork 子 agent 跑完后,它产生的新消息不会自动追加到父对话的 messages 数组里。子 agent 的输出只保存在自己的对话存档里,或者通过写文件这种副作用保存下来。

多数情况下主 agent 视角不变,fork 子 agent 是无感的后台任务。

围绕 cache 共享的工程设计

机制本身就这么简单,但直接照搬历史去 fork 会很贵。每次 fork 都把累积的几万 token 历史按正常价格全价编码一遍,后台任务如果都这么干 token 成本会非常高。

这里就要靠Prompt Cache 把成本压下来。Claude Code 的解法是让 fork 子 agent 的请求完全命中主对话已经建好的 cache,让父对话的写入成本被复用。

不过 cache 是按一个完整的 cache key 匹配的,不只看 messages。

给 Claude Code 发一句 Hi,会发生什么 里拆过一个完整请求的结构,简单说,system prompt、tools 列表、model 配置和 messages 前缀任意一项不同,cache key 就是新的,整段历史都得重新计费。

所以 Fork Agent 强制让这些参数和父对话保持一致。整个 fork 请求里只有最后追加的那条 user 指令是新的,fork 一次几乎只新增最后那条 user 消息的输入成本,前面所有历史都按 cache read 价格计费。

这套一致性约束有个反直觉的副作用:很多看起来「好心办坏事」的优化反而会打破 cache。

最典型的例子是工具权限的处理:fork 子 agent 继承了主 agent 的完整工具列表,理论上它能读文件、改代码、跑命令。但在大多数场景里,你并不希望一个后台 agent 拥有这么大的权力,万一它判断失误改坏你的代码呢?

最直观的「禁用工具」做法是把 tools 列表传空数组。但 tools 列表也在 cache key 里,传空一样会让整段历史 cache miss。

所以 Claude Code 在权限管控这件事上走了另一条路:保留 tools 不变,在工具执行环节做约束。

tools 列表原封不动留在请求里,确保 Prompt Cache 能够命中;具体调用某个工具时,按场景限制工具的使用,如果模型调用了未经授权的工具,会拒绝执行。

常用使用场景

不同工具都可以把 fork 用在临时提问、分支探索或后台任务上,只是入口和具体实现不同。下面继续以 Claude Code 为例,看看它实际使用 fork agent 的几个场景。

其中第一个场景在 Codex 中也有对应能力;后两个场景则主要用来说明 Claude Code 的内部实现。

场景一:临时提问

最直观的场景是:主 Agent 正在工作时,临时开一个旁支问问题,但不打断主对话。

想象 Claude 正在帮你重构一个模块,过程中它用了一个你不太熟的库或 API。你想顺便问一下这是什么东西、怎么用、原理是什么,但又不想打断它正在干的活。

新开一个会话问吧,要把当前讨论的背景重新讲一遍。直接在主对话里问吧,又会把主线打断,上下文被这个临时问题污染。

Claude Code 的 /btw 和 Codex 的侧边栏会话都是为这个场景设计的。

在 Claude Code 中输入 /btw 你的问题,它会 fork 一份当前对话,让子 agent 在单轮内回答这个问题(任何工具调用一律拒绝,只能基于已有上下文直接回答)。在 Codex 中,可以直接打开侧边栏临时会话;如果使用 Codex CLI,对应命令是 /side 你的问题。

两者的界面不同,但效果类似:旁支会话继承当前上下文,产生的消息保存在独立记录里,结束后回到主对话继续。下面的组件展示的是 Claude Code 的交互方式:

Claude Code 的这个 fork 对话可以共享主对话的整段历史 cache,所以响应速度比较快,新增的 token 花费也比较少。

场景二:后台自动 memory 提取

Claude Code 有个自动记忆系统:你和它聊天时,它会自动从对话里提取值得记下来的内容(你的偏好、项目约定、用过的技术方案等),存到 memory/ 目录的文件里,下次对话时再加载回来。

这个场景用 fork 的理由有两个。

一是判断「有没有值得记的」必须看完整对话,二是这是个完全后台、用户无感的任务,共享主对话 cache 是关键。

限权方面:搜索和阅读类工具(Read、Grep、Glob)完全放开,子 agent 才能去翻代码、查上下文判断「这段值不值得记」;Bash 只允许只读命令;唯一能写文件的 Edit/Write 严格限制在 memory 目录内,其他位置一律拒绝,保证后台 agent 不会去改你的项目代码。

Claude Code 的自动记忆系统 会详细介绍自动记忆系统的实现,本文不再展开。

场景三:后台 Sub Agent 的进度摘要

Sub Agent:上下文管理的艺术 那篇讲过,主 agent 可以并行启动多个 sub agent 各自干活,每个 sub agent 在隔离的上下文里跑。

主 Agent 不会实时盯着后台 sub agent 的干活,因为读取它们的完整输出都是要占用主 Agent 的上下文的。

但主 Agent 可以时不时帮你查一下后台的 sub agent 的工作进度。这底层就用到了 fork 机制:

fork 子 Agent 的对话,总结出当前工作的摘要(比如「正在读 xxx 文件」「正在跑 XX 模块测试」),发送给主 Agent。这样主 Agent 就能低成本监控后台 sub agent 的工作进度了。

这个 fork 的限权和 /btw 类似:所有工具调用一律拒绝,唯一能干的事就是基于已经看到的历史写一句话总结。

主 Agent 可能会高频轮询(每 30 秒一次)工作进度,如果不用 fork 机制共享 Prompt Cache,成本会很高。

总结

Codex 和 Claude Code 的 fork 机制在原理上很接近:复用已有对话历史,在此基础上追加新的任务指令,同时把后续消息留在独立分支中,避免干扰主对话。

Claude Code 可以用 /btw <问题> 临时提问;Codex 可以使用侧边栏临时会话,在 Codex CLI 中对应 /side <问题>。如果希望把旁支长期保留下来继续探索,Codex 还可以使用 /fork 创建新的会话分支。

本文主要以 Claude Code 为例,是为了拆解它围绕 Prompt Cache 和权限约束所做的工程设计。理解这套机制,也能帮你看懂 Codex 侧边栏会话这类功能为什么可以继承当前上下文,又不把临时问题塞回主对话。

相关推荐
小小、码农2 小时前
[特殊字符]【C++11】类的新功能全面解析:移动构造、移动赋值、default、delete、final 与 override
linux·开发语言·网络·c++
Dovis(誓平步青云)2 小时前
逆变器报警却找不到板子,热斑图怎样定位到具体组串
java·开发语言·人工智能·机器学习·卷积神经网络
DeeGLMath2 小时前
optimtool最新版研究梯度优化
python·optimtool·梯度优化
坐吃山猪2 小时前
Python-Hermite基函数
开发语言·python
空~自由3 小时前
Python 正则 . * ? .* .*? (.*?) 通俗讲解
开发语言·python
Web3&Basketball3 小时前
DeepSeek V4.1 Flash 实测:Token 砍半、Search API 跑通
数据库·人工智能·python·大模型·api调用·deepseek·推理优化
hpoenixf3 小时前
代码越来越便宜,为什么系统却越来越难升级?
agent
墨子夕3 小时前
设计模式——Java × Python 双语言精讲 · 23 个经典模式 + 现代演进(第一篇)
java·python·设计模式
程序 代码狂人3 小时前
经典 SQL 面试题:连续登录问题的两种经典解法
java·开发语言·算法