拆.
主循环——AI 怎么'思考'一轮对话 · 第 07

PRUNE_PROTECT 只保护 Skill 不保护 Tool 输出

'持久知识'和'临时数据'的边界

4500读完约 23 分钟craft:B+发布于 2026-06-21

一、原理:内容的"半衰期"差别

2.4 章我们讲过 opencode 的 compaction 三态——PRUNE_MINIMUM、PRUNE_PROTECT、DEFAULT_TAIL_TURNS。这一章我们走深一层——讲 PRUNE_PROTECT 内部的一个细节

packages/opencode/src/session/compaction.ts 第 41 行有一个常量:

const PRUNE_PROTECTED_TOOLS = ["skill"]

这个常量只有一个元素——"skill"。它的含义是——compaction 时被特别保护、不参与压缩的工具只有 skill

注意它不包括 read、write、edit、bash、grep、glob、task、todowrite 这些常用工具——它们的输出在压缩时正常参与压缩

为什么 skill 特殊?为什么其他工具不保护?

要理解这个判断,要先理解一个核心概念——内容的"半衰期"

不同内容在 session 里的"价值衰减速度"不一样。我们看几个例子:

Tool read 的输出——LLM 用 read 工具读了一个文件。这个文件内容现在是"工作记忆"——LLM 当前正在用它思考。但 30 分钟后这个内容的价值大幅下降——LLM 已经基于这个内容做完决策了,留着它只是历史。半衰期:短

Tool bash 的输出——LLM 跑了一个测试命令,看到输出。这个输出对当前决策有用,但跑过的命令通常不再重要。半衰期:短

Tool task 的输出——subagent 返回的结果。这是"分包出去的工作"的产物,主 agent 接收后整合到自己的判断里。结果本身的价值就在那次整合——后续可以丢。半衰期:短

Tool todowrite 的输出——是当前待办清单。但 LLM 每次 update 都会全量替换——所以历史的 todowrite 输出已经被新的覆盖了。半衰期:极短

Tool skill 的输出——LLM 加载了一个 skill。skill 内容是"专业指导"——告诉 LLM 在某个领域应该怎么做事。这种指导跟当前任务的整个生命周期相关——LLM 在做这件事的过程中一直需要参考。半衰期:长

把这种"半衰期"差别画出来:

工具内容类型半衰期该不该压缩
read文件内容可压(保留摘要)
write/edit修改 diff可压(保留 file 路径)
bash命令输出可压(保留 exit code)
grep/glob搜索结果可压
tasksubagent 结果可压
todowrite待办清单极短可压(最新版有效)
skill专业指导不能压

这就是为什么 PRUNE_PROTECTED_TOOLS = ["skill"]——其他工具的输出半衰期短,可以摘要代替;skill 的输出半衰期长,必须完整保留。

这是个内容分类的产品判断。它隐含的是 opencode 团队对"什么是 AI 长期需要的 vs 什么是临时工作的"的判断。

二、案例:skill 为什么特殊

Skill 在 opencode 里是个独立的概念——不是普通的 tool。第 8.3 章会专门讲它。这里我们简要看一下为什么它跟其他 tool 不同。

普通的 tool(read、edit、bash)干的是"具体操作"——读一个特定文件、改一段特定代码、跑一个特定命令。它们的输出是"做了什么的报告"。

Skill 干的是"提供指导"——告诉 LLM "如果你在做 X 任务,应该按 Y 步骤、注意 Z 细节、避免 W 陷阱"。它的输出是"工作方式的指导"。

opencode 在 .opencode/skills/ 目录提供了内置 skill。比如有个 effect skill 专门指导怎么用 Effect 框架写代码:

# Effect Skill

When working with Effect v4 / effect-smol code in this repo:

## Source Of Truth
Do not answer from memory. Always verify Effect API usage by 
reading the source or recent type definitions.

## Guidelines
- Use Effect.gen(function* () { ... }) for multi-step workflows
- Prefer Schema for API shapes
- Use Layer.effect() for service composition

## Testing Patterns
- Use Effect.runPromise() to test async effects
- Use Layer.merge() to compose test dependencies
...

如果 LLM 在编辑 Effect 相关代码,它会通过 skill tool 加载这个内容。加载后的指导在整个任务里都需要参考——不能在中途被压缩掉。

如果压缩了会怎样?LLM 编辑代码到一半,前面 skill 提到的"Use Schema for API shapes"被压缩掉了——LLM 可能突然忘了这条指导,写出不符合规范的代码。

这就是 skill 必须保护的原因——它是"任务级"的指导,不是"step 级"的数据

对比 read 输出。LLM 读了一个文件 A,看完做了决策。这个决策已经反映在 LLM 后续的输出里——"我看了 A,发现问题在第 30 行,准备修"。这段叙事被保留就够了——A 的完整内容可以压缩成摘要"已读 A 文件,定位 bug 在第 30 行"。

LLM 后续不需要再"看" A 的完整内容——它已经基于 A 做完了上下文转换。

但 skill 不一样——LLM 在编辑代码的整个过程里反复参考 skill 的内容。每写一段新代码都要"对照 skill 看看符不符合规范"。这种"反复参考"的内容不能被摘要——摘要丢的细节就是规范本身。

所以 opencode 的判断是——skill 是"持久知识"、其他 tool 输出是"临时数据"。持久知识保护、临时数据可压。

这个判断只是 opencode 的——不是所有 AI Agent 都这样选。Cursor / Claude Code / Devin 可能有自己的"持久 vs 临时"分类。但任何严肃的 AI Agent 都必须做这个分类——否则 compaction 会丢关键信息或者保留太多。

三、设计启示:"持久知识"和"临时数据"的产品判断

这一章的核心论点:Compaction 不是无差别压缩——要识别哪些内容是"持久知识"、哪些是"临时数据",然后区别对待

如果你做 AI 产品需要 compaction,下面的判断框架有用。

判断 1:这个内容是"任务级"还是"step 级"?

任务级——整个任务过程中都需要参考。比如:

  • 用户最初的需求描述
  • 关键的技术约束
  • 已确认的设计方案
  • 加载的 skill / 专家指导
  • 用户明确的偏好("代码风格用 X、命名用 Y")

step 级——只在某一个步骤需要。比如:

  • 读取的某个文件内容(步骤完成后只需要摘要)
  • 跑的某个命令的完整输出(步骤完成后只需要 exit code)
  • 搜索的某次结果(步骤完成后只需要"找到的关键文件")

任务级内容必须保护,step 级内容可以压。

判断 2:这个内容会被"反复参考"还是"一次性消费"?

反复参考——LLM 在多个 step 里都要回看。这种内容必须留着。

一次性消费——LLM 看一次后做完判断就不再看。这种内容可压。

skill 是反复参考的,read 输出是一次性消费的。

判断 3:这个内容能不能用"摘要 + 引用"代替"完整内容"?

如果可以——压。比如 read 输出可以变成"已读 A 文件(共 500 行),关键逻辑在 30-50 行"——后续 LLM 想看具体内容可以再 read 一次。

如果不可以——保留。skill 的指导没法"摘要 + 引用",因为 LLM 反复参考的是具体的细则。

判断 4:这个内容如果丢了,LLM 能从哪里再"找回来"?

如果能从文件系统找回——可压。read 的内容可以再 read。

如果只在 message 历史里——不能压(除非确实可丢)。

skill 的内容如果丢了,LLM 必须再 call skill tool 加载——成本不低,所以不如直接保留。

判断 5:这个内容是"系统提供的"还是"用户/LLM 生成的"?

系统提供的(AGENTS.md、skill、tool description)通常是任务级——保护。

用户/LLM 生成的(user message、assistant response)通常是 step 级——可压(但保护最近几个 turn)。

这个判断有例外——用户最初的 message 是任务级的("帮我重构这个模块"),后续的 message 大多是 step 级。所以保护要细化到具体 message——不是简单的"用户消息全保护"。

把这 5 个判断用到你的 AI 产品:

  • 用户输入的需求规范——任务级、反复参考、不能摘要、丢了要重问、用户生成 → 保护
  • LLM 探索文件结构的 grep 结果——step 级、一次性、可摘要、能再 grep、LLM 生成 → 可压
  • LLM 加载的 domain knowledge skill——任务级、反复参考、不能摘要、能但贵再加载、系统提供 → 保护
  • LLM 调用 task subagent 的返回——step 级、一次性消费、可摘要、丢了重跑、LLM 生成 → 可压
  • 用户明确的设计约束("不要用 TypeScript any、不要 break 现有 API")——任务级、反复参考、不能摘要、丢了 LLM 会犯错、用户生成 → 保护

这种分类让 compaction 真正"压对地方"——不是无脑压所有历史,是识别价值、保护核心、压缩冗余

最后一个观察。这个细节——PRUNE_PROTECTED_TOOLS = ["skill"]——在 opencode 的代码里只是一行常量。但它背后是 opencode 团队对**"AI 长期需要什么"**的深度判断。

很多 AI 产品在做 compaction 时没做这种判断——一律按 token 数压、一律按 message 顺序留。结果用户经常遇到"AI 突然忘了关键约束"——因为关键约束被压在了 message 历史里没有特殊保护。

把"持久知识"和"临时数据"的边界画出来——是 AI 产品工程师的必修课。这是 opencode 的内容分类哲学,也是这一章想传递的核心方法论。

下一章 2.8 我们看 turn 的另一个边界——怎么决定一个 turn 结束了。终止判定是个被严重低估的设计问题。