跳转至

Harness 工程实践复盘:100% Cache 命中的 Agent 怎么设计?

Ch05.074 Harness 工程实践复盘:100% Cache 命中的 Agent 怎么设计?

📊 Level ⭐⭐⭐ | 23.7KB | entities/openclacky-harness-engineering-100-percent-cache-hit.md

原文存档

背景与核心结论

ClackyAI 团队近期拿 4 家 Agent 做了一次横向测评,结果发现:同样的 prompt、同样的模型、同样的任务,成本最高可以相差 6 倍,且能与 Claude Code 保持同等能力。Harness 工程的水平,才是 Agent 产品真正拉开差距的地方。

这个结论颠覆了行业对"模型能力决定一切"的认知。在模型能力快速趋同的背景下,工程化水平正在成为差异化竞争的核心要素——而 cache 命中率是其中最关键的技术杠杆。

两代失败教训

第一代:RAG / 知识库

把用户代码库、文档、历史会话全部 embedding 进向量库,检索 + 重排 + 改写查询。实际跑下来三个致命问题:向量更新成本高且实时性差;90% 的召回率对 Agent 场景完全不够用(判断 97% 才刚刚够用);多了一个会挂的部件,延迟也上来了。

结论:不要搞 RAG。如果要上 Agent,直接上 Agent,外加一个适合 AI 阅读的文档站就够了。

第二代:多 Agent 工作流

Planner、Coder、Reviewer、Tester 各一个 agent,消息总线编排。结果是:每个 sub-agent 各有 cache 命名空间,交接一次就 miss 一次;单 agent 4 分钟能完成的任务,多 agent 编排到 14 分钟,成本翻 6 倍;SWEBench 分数能刷上去,但实际用户体验脱节得厉害。

结论:不要做多 Agent 编排。人类的分工逻辑不适用于 AI——AI 不需要「一个人想、一个人写、一个人审」,一个足够好的 agent 加一套足够好的 harness 就够了。Benchmark 跑分也不重要,模型每半年跨一个台阶,用工作流堆出来的分数会被下一代模型 + 朴素 harness 直接抹平。

7 个关键工程决策

决策 1:双 Cache 标记

大模型的 prompt cache 是按前缀匹配的——前缀里改一个字节,从那里往后全部失效。最直觉的做法是每轮在消息末尾打一个标记。但这个做法在三个场景下会失效:历史消息追加后原标记位置的内容变了;模型回退一次工具调用后标记直接作废;切换模型时标记抖动导致额外的 miss。

做法:每轮标两条连续消息,形成一个滚动双缓冲。 任何时刻都持有两个断点,一个读一个写。下一轮把「读」再读一次,在新尾部写一个新的。这样即使模型回退了一步,倒数第二个标记仍然落在有效消息上——单步回退仍能命中。

为什么是 2 不是 3? 双标记正好覆盖「旧尾部 / 新尾部」这一个边界,第三个标记落在更前面的位置,对应的 cache 段永远会被前两个覆盖——多写一次白花钱。

这个设计的精妙之处在于:它用最小的标记数量实现了对 cache 边界变化的完全覆盖。双缓冲模式保证了任何一个时刻都有一个"热"的断点可以继续使用。

决策 2:System Prompt 字节冻结

OpenClacky 的 system prompt 在 session 启动时一次性构建,之后一个字节都不动。这是 cache 命中率的第一道地基——system prompt 一变,后面所有 cache 全废。

日常运行中至少有四类信息「天然想插进 system prompt」:当前时间、当前模型、新装的 Skill、用户偏好更新。如果真写进去,任何一次变更都是全量失效。

做法:把这些动态信息写成一条普通消息插进对话历史,打上「系统注入」标签。 它不会被 cache 标记选中,不会被算作真实用户轮数,压缩时也不会原样搬进新历史。同一天内只注入一条,跨天或切模型时再插一条新的。

代价:session 中途装的新 Skill,当前 session 里看不到,要开新 session 才能用。 接受这个摩擦——装 Skill 是低频操作,cache 命中是每轮都在享受的收益。

这个决策体现了Harness Engineering的核心哲学:把会变化的东西和不会变化的东西严格隔离,让不变的部分最大化享受 cache 红利。

决策 3:Skill 子 Agent 架构

invoke_skill 是整个 OpenClacky 最核心的设计。它启动一个子 agent,子 agent 拥有跟主 agent 完全相同的工具集,执行完后把结果返回给主 agent。主 agent 的历史里只看到一对「调用 → 结果」消息。

这个设计解决了好几个问题。状态隔离。 做代码审查的 Skill 可能需要读几十个文件、跑大量搜索、输出长篇分析。这些中间过程隔离在子 agent 的 session 里,主 agent 的历史没有被污染——cache 命中率不受影响,压缩也不会被提前触发。

动态加载,不改工具列表。 装新 Skill 就是放一个文件到指定目录。invoke_skill 这个工具本身始终存在,Skill 的内容是调用那一刻才读取的。不需要改 system prompt,不需要改工具 schema,不需要重启 session。

能力可以无限扩展,但工具数始终是 16 个。 代码探索、记忆召回、PPT 生成、部署上线——这些能力全部是 Skill,通过 invoke_skill 这一个工具入口调用。

这与多 Agent 系统的常见范式不同:不是通过增加 Agent 数量来扩展能力,而是通过 Skill 子 Agent 架构实现能力的可扩展性,同时保持主 agent 的工具列表稳定。

决策 4:固定 16 个工具

工具 schema 紧贴 system prompt 之后,在 cache 前缀里。每多一个工具,不只多了 schema 的 token 成本,还多了「下次改工具时全量失效」的风险面。但工具太少也有代价:模型本来一步能做完的事要分好几步,轮次上去了,每轮都在付钱。

16 个工具: 文件读写 3 个、代码搜索 2 个、终端 1 个、浏览器 1 个、网络 2 个、任务管理 4 个、用户交互 1 个、Skill 调用 1 个、安全删除 1 个。

设计原则:参数尽量少(减少模型出错),粒度刚好够用(不冗余也不过度合并),每个工具有充分的测试覆盖(1600+ 测试用例)。 那些「看起来需要专用工具」的能力——代码库分析、记忆读写、浏览器多动作、sub-agent 编排、定时任务——全部通过 Skill 实现,不占工具位。这一套跑了 4 个月,没有需要加第 17 个工具的时候。

16 个工具的数字背后是一个经过长期验证的经验值。这个数字足够大,能覆盖主流程场景;又足够小,能保证 cache 前缀的稳定性。

决策 5:压缩不换模型,空闲时做

上下文窗口再大也会填满。压缩不可避免,但压缩是 cache 命中率最大的单点威胁:老消息被替换成摘要,前缀从那一刻起就不一样了,必然 miss。

不换模型压缩。 很多 agent 开一个独立的 LLM call 用小模型做摘要。问题是这个独立 call 跟主 session 没有任何共享前缀,压缩本身就是 100% miss;压完之后主 session 的历史也变了,又是一轮 miss。等于每次压缩付两笔钱。

做法:把压缩指令作为一条消息插进当前对话末尾,走正常请求路径。 压缩 call 命中现有 cache(只有尾部几百 token 的指令是冷的),压完后重建历史只 miss 一轮。对比独立 call 方案,一次 50K token 会话的压缩事件,冷 token 从 50000 降到 500。

空闲第 3 分钟启动压缩。 大模型厂商的 cache 有 TTL,一段时间无请求就过期。用户停止输入 90 秒后检查,如果历史接近阈值就立刻压缩——此时 cache 还是热的,代价极低。用户思考几分钟回来,看到的是一个已经压缩好、cache 已经 warm 的 session。不做这一步的话,用户回来面对的是 cache 过期的长历史,单那一轮可能就是 10 倍成本。

积极压缩而非用满上下文。 100 万 token 即使全部 cache hit,一轮也要付 10 万 token 等价的钱。策略是压缩后保持历史在 1 万 token 以内。短历史 + 高命中率,比长历史 + 偶尔 miss 便宜得多,效果也更可控。

压缩策略是推理优化在 agent 场景的具体应用:通过主动管理上下文内容,在成本和效果之间取得最优平衡。

决策 6:工具自进化

PDF、Excel、Word、PPT 的读取是 Agent 高频需求。内置专用工具会让工具列表膨胀(违背决策 4),做成 Skill 让用户手动装体验又差。

第三条路:首次安装时把一组 Python 脚本复制到用户目录,agent 需要读文档时用终端工具跑这些脚本。 工具列表没有增加。如果脚本跑不过(缺依赖、格式变了),agent 自己修改脚本、装依赖,下次就不会出问题。

处理文档的能力不是写死在代码里的,它活在用户目录的脚本里,agent 自己可以维护和进化。

这个设计体现了Agent 评估框架中的一个重要原则:能力应该具有可扩展性和自我修复能力,而不是通过增加工具数量来应对所有场景。

决策 7:内置浏览器,接管已有 Chrome

主流做法是 Headless 浏览器或外接 MCP 服务,两种都不用——内置了一个 MCP Client,直接接管用户已经在跑的 Chrome / Edge。

Headless 的问题: 用户不知道 agent 在干什么,出了问题无法判断,登录态和 cookie 也拿不到。外接 MCP 的问题: 安装成本高、稳定性不可控、工具 schema 不可控(外部 MCP 可能暴露几十个细粒度工具,直接打进工具列表就违背了决策 4)。

接管已有浏览器的好处: 用户看得见 agent 的操作、登录态和 cookie 直接可用、对外只暴露一个 browser 工具(snapshot / click / type / navigate 等动作都是这一个工具的参数),schema 稳定。代价是需要维护 daemon 的生命周期管理,但这是一次性的工程投入。

核心原则

把工程预算花在 Harness 上,把智能预算留给模型。

不做 RAG,不做多 Agent 编排、不做工具堆叠——不是因为这些东西没用,而是因为模型在快速变好。半年前需要 4 个 agent 协作才能通过的任务,今天一个 agent + 一套好的 harness 就能做得更快更便宜。

选择把精力放在那些不会随模型进步而过时的事情上:cache 命中率、工具稳定性、安装体验、压缩策略。这些是 Harness 层面的基础设施,不管模型换到哪一代都用得上。

关键洞察

Cache 局部性是最核心的设计维度

从双 Cache 标记、System Prompt 字节冻结、到压缩策略,OpenClacky 的所有工程决策都围绕一个核心:最大化 cache 局部性(cache locality)。prompt cache 按前缀匹配的特性,使得任何前缀变化都会导致后续所有 cache 失效。因此,设计的一切目标就是:让不变的东西尽可能早地进入前缀并保持稳定,让变化的东西尽可能晚地进入前缀并快速收敛。

固定工具列表是 cache 稳定性的保障

16 个固定工具的设计不仅降低了 token 成本,更重要的是保证了工具 schema 永远在 cache 前缀里。如果工具列表经常变化,每次变化都会导致 cache 全量失效。固定工具列表 + Skill 扩展能力的组合,是在稳定性和可扩展性之间取得的最佳平衡。

Skill 子 Agent 架构实现了真正的关注点分离

传统的多 Agent 协作让每个 sub-agent 拥有独立的历史记录,导致交接时的 cache miss。而 Skill 子 Agent 架构将子 agent 的执行结果压缩成一对「调用 → 结果」消息,主 agent 的历史结构完全不受影响。这种设计让 Skill 能力的扩展不会损害主 agent 的 cache 命中率。

深度分析

1. 成本差距揭示了 Agent 工程化是差异化竞争的核心要素

4 家 Agent 在相同 prompt、相同模型、相同任务下成本相差 6 倍,这一数据直接证明:在模型能力快速趋同的背景下,Harness 工程水平才是决定 Agent 产品成败的关键变量。6 倍成本差距意味着同样的毛利率目标,A 公司可以用 1/6 的收入覆盖等量调用,B 公司则需要 6 倍收入才能支撑同等模型调用量。在模型 API 定价趋向底部的行业趋势下,Harness 优化带来的成本优势会直接转化为商业竞争力。Agent 产品公司应该将工程化投入(cache 优化、压缩策略、工具稳定性)视为与模型能力投入同等重要的核心 KPI。

2. 两代失败揭示了 Agent 工程中的"能力扩展"与"状态膨胀"必须隔离

RAG/知识库和多 Agent 工作流两代失败方案有一个共同的根本问题:能力扩展必然带来状态膨胀——RAG 让向量索引随知识库增长而变慢,多 Agent 让 sub-agent 历史污染主 agent 上下文。OpenClacky 的 Skill 子 Agent 架构通过"调用 → 结果"消息对实现了能力扩展与状态膨胀的彻底隔离:主 agent 历史结构永远不受 Skill 执行过程污染。这意味着 Agent 工程的核心架构原则应该是:任何能力扩展模块(Skill、Tool、MCP)都必须设计为对主 agent 历史状态零污染的接口,否则上下文管理会随功能增加而持续恶化,最终导致系统不可用。

3. 双 Cache 标记设计体现了"最小化冗余"原则在分布式系统设计中的普适性

双 Cache 标记为什么是 2 而不是 3?因为第三个标记对应的 cache 段永远会被前两个覆盖——多写一次白花钱。这个分析逻辑与分布式系统中的"最小化复制因子"原则完全一致:在保证系统弹性的前提下,复制因子应该尽可能小。双标记覆盖了"旧尾部 / 新尾部"这一个实际边界,三标记则引入了冗余。这个原则在 Agent 工程中有广泛适用场景:工具参数设计(参数尽量少)、压缩策略(保持历史在 1 万 token 以内)、system prompt 冻结(动态信息不入 prompt),都是在各自维度上实践"最小化冗余"原则,以换取系统整体的可预测性和效率。

4. System Prompt 字节冻结揭示了"变化源隔离"是复杂系统稳定性的根本

OpenClacky 将动态信息(当前时间、模型版本、用户偏好)从 system prompt 迁移到对话历史中的一条标记消息,这一设计的本质是"变化源隔离"。System prompt 构成 cache 前缀的最稳定部分,任何变化都会导致全量 cache 失效;而对话历史中的动态信息是"变化预期内"的部分,可以接受频繁更新。将两者隔离意味着:稳定的基础设施(system prompt + 工具 schema)最大化享受 cache 收益,变化的上下文(时间、偏好)以最低成本更新。这与计算机系统设计中"将经常变化的模块与稳定模块解耦"的原则完全一致,Agent 系统的任何动态配置都应该遵循这一原则进行架构设计。

5. "把工程预算花在 Harness 上,把智能预算留给模型"是 AI 时代的正确投资配比

OpenClacky 明确反对在工具数量增加、多 Agent 编排、工作流优化等方面投入过多工程资源。其核心理由是:模型能力正在快速提升,半年前需要复杂工作流才能通过的任务,今天一个朴素 harness 就能完成。这意味着投资在工作流上的工程预算会随模型迭代而快速贬值。相比之下,cache 命中率、工具稳定性、安装体验、压缩策略这些基础设施,无论模型如何迭代都有持久价值。这一投资逻辑对 AI 时代的产品研发策略有重要启示:优先投资那些不会随模型进步而过时的基础设施能力,而非在当前模型能力基础上构建的临时性工程优化。

实践启示

1. 将 cache 命中率作为 Agent 产品的基础指标进行持续监控和优化

OpenClacky 的实践表明,cache 命中率是 Agent 成本最关键的杠杆。对于生产环境的 Agent 产品,应该建立 cache 命中率的实时监控仪表盘,持续追踪:每日/每周 cache 命中率趋势、压缩事件导致的命中率波动、不同 session 时长下的命中率变化。当命中率出现显著下降时,应该能够快速定位是 system prompt 变化、工具列表变更、还是压缩策略失效导致的。这一指标应该与模型调用量、成本、人均任务完成数一起构成 Agent 产品运营的核心监控看板。

2. 优先实现 System Prompt 字节冻结,再逐步优化其他 cache 环节

System prompt 冻结是 cache 命中率的第一道地基。在构建 Agent 系统时,应该首先实现 system prompt 的一次性构建和永不修改机制:所有动态配置(当前时间、模型版本、用户 ID、偏好设置)都应该作为带标记的特殊消息插入对话历史,而非直接写入 system prompt。这个设计决策应该在系统架构早期确定,因为后期修改 system prompt 冻结策略会涉及历史 session 的兼容性问题。一旦地基打好,后续的压缩策略优化、cache 标记设计才能在其基础上发挥作用。

3. 用 Skill 子 Agent 架构替代多 Agent 编排来扩展 Agent 能力

多 Agent 编排的成本是每个 sub-agent 交接时的一次 cache miss,6 倍成本差距很大程度来自此。正确的扩展路径是:主 agent 保持工具列表稳定(OpenClacky 是 16 个固定工具),所有能力扩展通过 Skill 子 Agent 实现。Skill 在执行后只向主 agent 返回"调用 → 结果"消息对,不污染主 agent 历史上下文。这意味着构建 Agent 系统时,应该先花足够多的时间打磨主 agent 的工具集设计(OpenClacky 验证了 16 个工具可以覆盖主流程),然后通过 Skill 扩展来满足长尾需求,而非一开始就堆叠大量专用工具或构建复杂的多 Agent 协作流程。

4. 上下文压缩应在用户空闲时主动执行,而非被动等待 context 窗口耗尽

空闲第 3 分钟启动压缩是一个被低估的设计:用户停止输入 90 秒后检查并执行压缩,此时 cache 仍然是热的。如果不这样做,用户思考几分钟后回来,面对的是 cache 已过期的长历史,单轮成本可能暴增 10 倍。对于构建 Agent 产品而言,应该在用户交互流中实现空闲检测机制:当用户一段时间无输入时,主动触发压缩逻辑,为用户的下一轮输入准备好 warm 的短历史上下文。这比等待 context 窗口耗尽后被动压缩更优,因为被动压缩时 cache 已经冷了,成本代价更高。

5. 工具列表应该追求"足够用"而非"全覆盖",接受有限工具集合的摩擦成本

OpenClacky 16 个固定工具的设计背后是一个经过验证的经验值。这个数字足够大能覆盖主流程,又足够小能保证 cache 前缀稳定。对于构建 Agent 系统的团队,这意味着:工具列表设计不应该追求"每个可能的场景都有专用工具",而应该追求"日常高频场景的最优覆盖"。长尾需求通过 Skill 实现,而非通过增加工具数量实现。接受一个现实:总会有工具列表覆盖不到的边缘场景需要用户或 Agent 用变通方法绕过去——这不是系统缺陷,而是固定工具列表设计的必然代价。4 个月验证没有需要第 17 个工具,说明大多数主流程场景确实可以用有限工具集覆盖。

相关实体