🍅
Claude Code 源码拆解 harness 设计 - 上下文工程
- 总结:从微压缩的零成本规则清理、会话记忆的结构化提取、完整压缩记忆摘要再到熔断保护,每一层都在假设上一层可能出现问题,每一层的成本和使用场景也各不相同,贯彻了上下文是稀缺资源的理念,关键不是上下文装不装的下而是该不该装进去
- Claude Code 的上下文压缩管理分为四层
- 微压缩(压缩成本最低,不调用模型,纯规则驱动)
- 清理原则:按工具类型白名单,保留最近 n 个工具结果,将更早的结果清理掉
- 白名单包括 FileRead、Bash、Grep、Glob、WebSearch、FileWrite 等, 这些工具的输出通常很大但是时效性很短,十分钟之前读取的内容很可能后续就不需要了
- 清理策略:微压缩在清理上下文的时候要考虑,清理掉这个工具的记录会不会导致服务端的缓存失效
- Anthropic 的 api 有一个 prompt cache 的机制,发送到服务端的 prompt 前缀会被缓存在服务端,下次请求如果前缀相同,不需要重新处理节省 token 节省时间(类似KV cache)
- 根据缓存的状态有两个清理路径
- Cached Microcompact
- 如果用户连续对话中,服务端缓存仍然有效,则不会修改本地消息内容,将原本包含工具输出的结果发送给服务端
- 通过 Cache Editing API 告诉服务端删除指定的工具结果,在命中缓存后 传入模型前清理掉不需要的工具输出,但不动 prompt 前缀 缓存继续有效
- 它维护一个全局状态追踪所有已注册的工具结果,按计数阈值决定哪些该删
- Cached Microcompact 只对主线程生效,因为Claude Code 会 fork 子 Agent 来执行 话记忆提取、prompt 建议等后台任务,如果这些子 Agent 将自己的工具结果注册到全局状态中主线程尝试删除的时候,会去删除一些在自己对话历史中根本不存在的工具,因此源码中用 QuerySource 做了主线程隔离
- Time-based Microcompact
- 如果用户离开了一段时间,服务端缓存已经失效,则直接修改本地消息内容,系统计算距离上一条消息的时间间隔如果超过阈值就直接修改消息内容,将旧的工具输出替换成一个占位符
- 会话记忆压缩(提炼结构化事实,成本低且保留最近消息的完整细节)
- 核心思路:不会将会话做摘要,而是从对话中提取
学到了什么 例如 项目结构、用户偏好、任务进度,这些结构化的事实会持久化到本地的记忆目录中(memory.md)
- 先等待后台的记忆提取完成,然后读取 Memory.MD 的内容,用这些结构化的记忆来代替传统的对话摘要
- 关键设计是 保留多少最近的消息,源码中有三个配置参数
- 最小 token 数量 默认 1w
- 最小 消息 数量 默认 5条
- 最大 token 数量 默认 4w
- 系统从最后一条已总结的消息开始往前扩展,直到同时满足
最小 token 数 和 最小 消息 数 或者 触发最大 token 数的上限
- 边界处理
- 扩展时 不能把 tool use 和 tool result 拆开 :如果保留了 tool result 就必须保留 对应的 tool use 的完整消息
- 处理 thinking block :流式传输时 同一个 message id 可能被拆分成多条消息 thinking 在一条 tool use 的另一条 不能只保留一个
- 压缩为后台异步提取,主线程会 fork 出 子agent 来完成这个任务,主线程没有模型调用,感受不到任何阻塞
- 完整压缩(调用模型,做会话摘要,成本高)
- prompt 设计:要求模型按九个维度来总结对话
- 用户的请求和意图
- 关键技术概念
- 文件和代码片段
- 错误和修复
- 问题解决过程
- 所有用户消息
- 待办任务
- 当前工作
- 可选的下一步
- analysis 机制(内嵌的 Chain of Thought)
- 要求模型在 analysis 标签中做详细的分析,然后再在 summary 标签中输出摘要,analysis 在格式化时会被完全剥离,不会进入压缩后的上下文,作用只是留给模型在摘要前做两次不同维度的思考,提供摘要质量
- 压缩请求本身可能触发 prompt to long 错误,源码中的实现解决方法为:按 API 轮次分组,从最早的组开始丢弃,最多重试三次
- 压缩后善后
- 清空文件读取缓存
- 重新注入最近访问的文件内容 最多 5个文件 or 5w token
- 重新注入 plan 文件、skills、mcp工具、agent 列表
- 自动触发(什么时候做压缩)
- autoCompact 系统会在每次调用模型后检查当前 token 用量,如果超过了阈值 (上下文窗口 - 13000 token 的缓冲),则自动触发压缩
- 触发顺序为:先尝试会话记忆压缩,如果不可能用或者压缩后仍然超出再退回完整压缩
- 熔断:连续失败三次停止重试(汲取彩蛋中 浪费25w次api调用经验)
- fork 出来的 子 agent 的请求不会触发 自动压缩(与微压缩中Cached Microcompact的策略保持一致)
- Claude Code 工具系统管理
- Claude Code 内置了 80多个工具,不可能全部塞进上下文
- 核心工具启动时加载,扩展工具通过 Tool Search 按需发现和加载(渐进式加载理念)
- 每个工具用 Zod Schema 定义模型输入参数和输出的 json 结构,必须通过验证才能执行
- 如果工具输出太大,系统不会直接截断,而是存到外部的 tool result storage 给模型一个摘要 加上 一个指针 模型按需取用,而不是强制塞入一整块巨大的输出
- snipCompact:不做完整压缩,精准 裁剪掉 特定的消息段,释放 token
- 如何判断是否裁剪
- 已被消化的工具输出:如果在10对话轮前执行了一个 cat 或者 ls 这种命令则会被标记为 snip-safe 可以裁剪
- 中间执行步骤:比如 Windows中常见的调用一个工具错误,然后尝试使用 powershell 完成成功,则报错的这种中间执行步骤可以被标记为 snip-safe
- 彩蛋
- 源码中有一条注释 2026年3月10日 1279个会话出现50次以上的连续自动压缩失败,最多的一个会话失败了3272次,全局每天浪费25万次api调用
- 上下文管理不是一个设计好就永远不变的系统,它需要在生产环境中连续迭代