三种 Hidden Agent
title 是 LLM、summary 是 diff、compaction 是 LLM
一、原理:用户看不见的"系统级 AI"
你用 ChatGPT 时,每次新开一个对话,左边的列表里会自动生成一个标题——"Python 列表排序"、"React Hook 用法"、"周末计划"。你没让它起标题——它自己起的。
这就是一个hidden agent——你看不到它运行,但它在你每个 session 里都默默工作。
AI 产品里这种"用户不可见的系统级 AI 任务"很多:
- 自动给 session 起标题
- 自动给 session 写摘要
- 自动检测对话是不是该结束
- 自动判断用户是不是在问敏感问题
- 自动建议下一步该做什么
- 自动压缩对话历史(2.4 章讲的 compaction)
这些任务的共同特点是——不是用户主动要求的,是产品自己决定要做的。用户感知不到它们运行,但它们让产品体验更好。
设计 hidden agent 时,第一个判断是——这个任务真的需要 LLM 吗?
很多 hidden agent 任务用确定性算法就能做:
- 文件 diff——可以用 git 或 diff 库算,不需要 LLM
- 命令历史摘要——可以提取每个命令的 exit code 和关键输出,不需要 LLM
- 错误分类——可以用正则匹配,不需要 LLM
但是某些任务真的需要 LLM:
- session 标题——LLM 能理解"这段对话主要在讲什么"
- 上下文压缩——LLM 能判断"哪些信息重要、哪些可以丢"
- 意图识别——LLM 能理解用户模糊的表达
有些任务介于两者之间——可以用 LLM 但不一定需要。这时候要权衡——LLM 质量好但贵且慢,确定性算法快且便宜但可能不够智能。
opencode 在这件事上做了三个有意思的选择——它有 3 个 hidden agent,但只有 2 个调用 LLM。我们看具体是怎么分配的。
二、案例:opencode 的三个 hidden agent
opencode 的三个 hidden agent 在 packages/opencode/src/agent/agent.ts 里被定义为 hidden: true。
Hidden Agent 1:Title Agent——LLM 驱动
每个 session 创建后,第一条 user message 完成时,title agent 自动运行一次。它接收 user 的第一句话 + assistant 的第一段回复,让 LLM 生成一个 session 标题。
title.txt 的 prompt 是这样:
You are a title generator. You output ONLY a thread title. Nothing else.
Generate a brief title that would help the user find this conversation later.
Your output must be:
- A single line
- ≤50 characters
- No explanations
Rules:
- you MUST use the same language as the user message you are summarizing
- Never include tool names in the title
- Focus on the main topic or question the user needs to retrieve
- Keep exact: technical terms, numbers, filenames, HTTP codes
- Remove: the, this, my, a, an
- NEVER respond to questions, just generate a title
注意几个细节:
- temperature = 0.5——比标准 LLM 调用低(默认 1.0),让输出更稳定
- ≤50 characters 硬约束——这是给 UI 的约束。session 列表的标题宽度有限,长标题会被截断
- 同语言匹配——用户用中文聊,标题用中文;用英文聊,标题用英文
- 禁止 emoji——保持专业感
- 保留 exact identifiers——文件名、错误码这些"硬信息"必须原样
为什么 title 需要 LLM?因为标题的核心是理解对话主题——这是 LLM 强但确定性算法弱的能力。一段聊代码的对话,确定性算法只能提取关键词("function"、"const"、"error"),但 LLM 能识别"哦这是在 debug 一个 auth 流程"——这才是好标题。
Hidden Agent 2:Summary Agent——确定性算法(diff)驱动
这是最有意思的一个。Summary agent 在 user 完成第一个 turn 后运行(如果有文件改动)。它生成一个简短的"这次 session 做了什么"摘要。
听起来跟 title 类似,但实现完全不同——它不调 LLM。它直接用 git diff 算法计算 snapshot 之间的文件变化,生成摘要。
packages/opencode/src/session/summary.ts 里的 computeDiff() 函数:
computeDiff():
- 取第一个 user message 时的 snapshot
- 取当前的 snapshot
- 调 snapshot.diffFull(from, to) 算 diff
- 返回 file 列表 + 每个 file 的 additions/deletions
输出是结构化的——每个文件、加了多少行、删了多少行、status 是 added/modified/deleted。这种结构化输出比 LLM 写的"自由文本摘要"对 UI 更友好——可以做成表格、可以点开看 diff、可以分类排序。
这是一个被低估的设计判断。R5-B 阶段我研究时第一反应是"summary 是 LLM 生成的,跟 title 类似"。深入看才发现——summary 是 diff 聚合不是 LLM。这意味着:
- summary 不消耗 token
- summary 不会"瞎写"——它的内容 100% 来自文件系统的真实变化
- summary 跑得快——几十毫秒就完成
- summary 结构化——UI 能用它做丰富展示
为什么 opencode 选确定性算法?因为 session 的"做了什么"在 coding agent 场景里就是文件变化——这是个可以精确算的事实。用 LLM 写自由文本摘要会丢精度(可能漏文件、可能写错行数),用 diff 算法是精确的。
这跟 title 的处理形成了对比——title 需要"理解对话主题"用 LLM;summary 是"统计文件变化"用算法。同样是"摘要",因为内容性质不同,技术方案完全不同。
Hidden Agent 3:Compaction Agent——LLM 驱动
这是 2.4 章已经讲过的——用 LLM 把历史压缩成 anchored summary。
为什么 compaction 需要 LLM?因为它要做的是"理解对话历史然后写摘要"——这是 LLM 的强项。用算法可以删某些段落,但写不出"用户最初想做 X、过程中决定改成 Y、目前进度是 Z"这种叙事性摘要。
但 compaction 的 prompt 设计有意思——它把权限设成 { "*": "deny" }——这个 LLM 没有任何工具权限。它只能读历史、写摘要,不能调任何工具。
为什么?因为 compaction 是"内部任务"——它不应该做任何"对外可见"的事。把权限锁死避免了 LLM 在做 compaction 时"突然想动手做别的"——这是个安全防护。
三、设计启示:用 LLM 之前先问"非 LLM 方案行不行"
这一章的核心论点:不是所有的"AI 任务"都需要 LLM——用 LLM 之前先评估非 LLM 方案。
opencode 三个 hidden agent 的选择给我们一个框架。决策时问几个问题:
问题 1:这个任务的输入是"语言"还是"数据"?
如果输入是语言(一段对话、一个问题、一份文档),可能需要 LLM——理解语言是 LLM 的强项。
如果输入是数据(文件 diff、命令 exit code、时间序列、数字),不需要 LLM——确定性算法更准更快更便宜。
opencode 的 summary 输入是"两个 snapshot"——这是数据。所以用算法。 opencode 的 title 输入是"一段对话"——这是语言。所以用 LLM。
问题 2:这个任务需要"精确"还是"洞察"?
如果需要精确(不能丢信息、不能瞎编),用算法更好——LLM 会幻觉。
如果需要洞察(理解主题、抓重点、形成判断),用 LLM 更好——算法做不到。
opencode 的 summary 需要精确(不能漏文件、不能写错行数)——用算法。 opencode 的 title 需要洞察(理解对话主题)——用 LLM。
问题 3:这个任务每次都跑还是偶尔跑?
每次都跑的任务(每个 turn 都跑 title、每个 user message 都跑 summary),算法更经济——LLM 累积起来成本高。
偶尔跑的任务(context 超限才跑 compaction),LLM 更合适——单次成本不是问题。
问题 4:能不能用更便宜的 LLM?
如果非 LLM 不行,但任务相对简单——用 small model 而不是 main model。
opencode 的 config.small_model 让用户指定一个便宜模型(比如 Claude 3 Haiku)专门用于 system 任务。Title agent 跑 Haiku 比 Sonnet 便宜 12 倍——质量基本够用。
问题 5:这个任务可以缓存吗?
很多 hidden agent 任务的输出可以缓存复用——如果用户没改 session 的关键内容,旧的 title / summary 还有效。
不缓存意味着每次跑都付费——浪费。要让 hidden agent 知道"什么时候 input 变了需要重新跑、什么时候 input 没变可以复用上次输出"。
把这 5 个问题应用到你的 AI 产品:
- 自动标签分类——输入是文本(语言),需要洞察,每次跑——LLM
- 文件去重——输入是数据(hash),需要精确,每次跑——算法
- 用户分群——输入是行为数据(数据),需要洞察,偶尔跑——LLM
- 错误堆栈解析——输入是数据(栈trace),需要精确,每次跑——算法
- 内容审核——输入是文本(语言),需要精确(不能漏),每次跑——LLM(小模型)+ 算法(关键词)兜底
这种细致的判断让你的产品用 LLM 的地方真的需要 LLM——不浪费、不滥用。
最后一个观察。Hidden agent 是 AI 产品体验差异化的关键——但它们往往被忽视。用户看不见 hidden agent,所以产品团队也不投入。
实际上 hidden agent 决定了产品的"丝滑感"。一个 session 列表里有清晰的标题——是 title agent 在跑。一个文件 diff 视图清晰显示"这次改了什么"——是 summary 在跑。一个长 session 还能继续工作——是 compaction 在跑。
这些用户不会专门提到——但他们能感知到"这个产品很顺手"。这种"顺手感"的来源就是把 hidden agent 做好。
下一章 2.6 我们看 hidden agent 怎么跟主循环并发跑而不阻塞——这是 Effect FP 的 fork 模式登场的地方。