Claude Code:缓存优先 Agent Harness

目标说明

读完你应能独立完成四件事:

  1. 交代清楚, 关于两种启用的方式, 这两种方式分别是顶层的那种, 还有块级的那种, 另外, 还要说明前缀顺序, 也就是 `tools → → ` 这样的顺序。

  2. 依照官方定价详表去核对, 5分钟的写入倍率以及1小时的写入倍率, 还有缓存读取倍率, 并且从`usage`当中读取出来。

` / `ens`。

  1. 说出为什么在会话处在进行当中的时候, 在工具方面进行增加或者删减的操作, 将工具原本有的顺序弄乱,把时间戳放置到静态之中, 这类情况都会致使缓存体系被突破呢。

  2. 在自己构建的 Agent 当中, 去复现 Code 的三条约束, 其中一条是 Plan Mode 运用工具进行建模, 还有一条是以``实现代替删除工具, 另外一条是复用父会话的前缀。

规格先钉死(均来自官方文档):

可启用的方式分两种, 首先请求放在最顶层的"",这样做的话断点会自动落在最后一个能够进行缓存的块上, 并且会随着多轮的操作而向前移动;其次是将""放置在具体的block之上。这两种方式是可以组合着使用的, 不过会占用最多4个断点名额当中的一个。

  • **前缀顺序**: 有个叫 `tools` 的, 然后 空的两个位置状态, 是这样的。要是在它之前的任何一层出现了变化, 那么就会导致跟在它后面的每层和原来的情况不匹配啦。

, **TTL**: 默认时长为5分钟, 使用期间会自行刷新且进行使用可不追加费用;存在可选 **ttl**: `"1h"`, 写入流程时将依据更高倍率进行计算。TTL自针对其相应规定的写入/读取请求起始伊始启动累计计费计算, 流式生成时所耗费时间也会计入窗口期之内。

定价倍率, 相对 base input, 5 分钟 cache write 对应的是 1.25×, 1 小时 write 对应的是 2×, cache read 通常为 0.1×。官方定价表对Fable 5.1 / 5.1的cache hits记为0.025×, 比如base input为10 / MTok 时, hits为0.25 / MTok。本文聚焦,不展开型号发布细节。

  • **可观测字段**:`

进行书写的那个, ens用于读取的那个, 断点之后出现的未缓存的部分。它们的总量大致等同于这三者加起来的总和。

适用场景与边界

适合认真做缓存

请问你提供的内容是否准确完整呢? 感觉这段表述不太清晰, 不太能依照其精准改写。请你检查或进一步明确你希望改写的内容。

  • 有着长度较长的静态指令, 存在仓库约定情况(比如是 .md哈), 还具备共享知识库这点, 之后紧接着的是变化着的用户问题。

MCP 具备好些工具 , 却并不想要于每一轮之际 , 皆带上完备者 ;倾向选用短桩加以 tool , 而绝非于中途之时 , 自 `tools` 里头将其摘除。

处在需要Plan Mode的状况下, 进行只读探索时, 当存在切换行为的需求, 所依靠的并非是更换一整个完整系列进行定义的多种工具, 而是运用某个状态工具从而达成切换行为的目的。

后续聊的话上下文将满, 所以需要摘要, 调用的时候条件是必须要与那个父会话共享同样一个前缀。

不该指望缓存单独搞定

前缀比模型最小能够缓存的长度短, 要是达不到那个门槛, 那就不写缓存, 并且也不报错, 通过查看usage是不是双零来进行判断。

时而改变 `tools` 的定义, 之后更改其顺序, 接着变换名称, 并且有的时候还会在过程中增加或删减工具。

  • 把变化的时间戳、会话 ID、当前文件列表塞进「静态」。

用不同种类的模型, 持续应用同一个超长的会话: 缓存根据模型的类型进行隔离;为了节省费用更改到更小的模型, 或者需要对整个段落, 展开重新构建前缀字符, 这样一来, 费用反而会偏高。

另外开启一条「专门摘要」的申请, 采用不一样的、不带有 tools: 前缀的方式, 从首个 token 开始就进行分叉, 按照全额未缓存输入来计算费用。

风险提示

字节大小的缓存要求保持一致, 断点数量最多为4个, 窗口大小约为20个block, 断点本身无需进行收费, 读取与写入的token才是真正需要付费的部分按照 (也包括平台对应的隔离边界)为区间来实施隔离。缓存并不支持跨组织共享且不发生改变地保持输出采样方式。

并发场景当中还要留意, 缓存条目必须要等到第一次响应开始之后, 才能够被后续的请求读取到。要是刚开始就并行发起多条具有相同前缀的请求, 那么有可能全部会未命中、全部进行写入操作。采用预热或者串行首请求的方式会更加稳妥。官方同样提供了 `: 0` 的预热写法, 在共享静态段上进行显式断点, 占位 user 使用非空字符串就可以, 加以确认。

放真实流量要在之后, 预热请求的配置, 要和正式流量一样, 不然写入的前缀, 和正式流量就对不上了。

步骤:按 Code 思路搭一套缓存优先

  1. 先开 ,再按需加

多轮的场景之中, 顶层的东西, 通常情况下就已经是够用的啦: 系统会把那个断点放置在最后一个能够进行缓存的块那里, 然后随着对话的向前推进而移动。要是静态段跟动态后缀它们的变化频率是不一样的, 那么就在静态段的末尾又钉上一个断点, 以此来避免断点落在「每一轮都会发生变化」的块上面。

Code的布局方面的经验能够直接拿来用: 有关全局稳定的内容, 以及tools要放在最前面;项目级的约定, 像.md这种, 要放在其次;会话上下文要放在再其次的位置;真正的轮次, 才放在最后。如此这般, 跨会话、跨用户时也能够尽可能地共享处于最前面的前缀。一旦其顺序被「临时插入动态字段」给破坏掉了, 命中率便会出现断崖式的下跌。

官方文档之中的常见反例为, 系统上下文(块1至5)之后跟着带有时间戳的用户块(块6), 然而却将 `` 放置于块6处。每一轮的哈希均不相同, 并且也寻觅不到更早的写入点, 其结果便是每一轮都在进行写入操作、几乎不进行读取操作。修正的方法为, 将断点固定在最后一块跨请求保持不变的内容之上。所要找寻的是「此前请求在断点位置所写下的条目」, 并非「替你自动缓存断点之前看起来稳定的内容」。

  1. 示例 A: 多轮 + 读 usage

```

= .()

= (

不要存档你还没读过的内容, 不要提交你尚未阅读的文件, 但不要提交你未曾读过的内容。

TOOLS =

"name": "",

"": 从该处在 . 中读取一个文本文件。""。

"": {

"type": "",

"": {"path": {"type": ""}},

"": ,

},

},

"name": "",

"": {"type": "", "": {}},

},

"name": "",

"": {

"type": "",

"": {"plan": {"type": ""}},

"": ,

},

可选:把 tools 前缀钉住()

"": {"type": ""},

},

def turn(, *, ttl=None):

= {"type": ""}

if ttl:

= ttl # 例如 "1h"

resp = ..(

model="-opus-5",

=1024,

=, #

=

"type": "text",

"text": ,

"": {"type": ""},

tools=TOOLS,

=,

u = resp.usage

print(

"": u.,

"": u.ens,

"input": u.,

"": u.,

resp

=

第 1 步, 仅仅提前阅读以整理 src/auth 的登录进口处。第 2 步, 只进行读这一动作去梳理 src/auth 的登录入口处。

r1 = turn()

.({"role": "", "": r1.})

.({"角色": "用户""":"接着进行, 详尽罗列出与之相关的测试文件所对应的路径" })

r2 = turn() # 期望 明显上升

```

第一轮常见形态是 `

同前缀的后续轮次, 应看到 `ens` 上升, 0`;若读写全是 0, 先查最小可缓存长度, 与断点是否落在变化块上。

  1. Plan Mode:用工具建模,而不是换工具集

"一进入那个Plan Mode, 就会换成只读工具来看", 这看起来是挺干净的, 然而, 它却会把tools的前缀改掉, 会导致整段会话缓存全都作废。Code的做法是这样的: 工具定义一直都会在现场;那两个反引号夹着的斜线本身就是工具。人进入之后, 要靠系统那边注入的说明, 或者是下一条user里的标签来约束"只进行探索、不能改文件", 等退出的时候再把计划交上去。

附带额外的收益, 该模型能够自行调用, 去处理那些棘手的难题, 而无需宿主去修改请求体, 这是其一。其二是, 在进行自建操作的时候, 要将「模式」打造成工具或者消息标签, 并且一定不要把它做成「另一份 tools 数组」, 就是这样。

如下这种同类模式, 还能够推广至「只读审查」「发布冻结」等状态: 其运用进入/退出工具去表达状态机, 将执行策略书写在消息当中, 工具清单维持恒定, 以此方式, 产品功能能够增添, 缓存前缀无需每次都跟着改变。

  1. ``:短桩代替删除 MCP 工具

MCP数量一旦增多, 每一轮携带完整的就会变得很贵;在中途删掉工具又会导致打穿前缀。Code使用冒号: 在请求里始终放置同一批短桩(通常先给出名称, 并标注为: true), 在需要的时候再通过工具拉取完整定义。短桩的集合以及顺序保持不变, 缓存前缀就能稳定。

表明, (针对字段名而言, 其具体应以你所运用的SDK或者平台文档作为依据;此处所传达的意思是一种限制, 并非是第二套官方的API规范)。

```

=

"name": "",

为空, "Jira(通过工具完整列出)"。

"": {"type": "", "": {}},

"": True,

},

"name": "",

"": "Fetch a pull by .",

"": {"type": "", "": {}},

"": True,

},

核心工具 + 短桩:顺序固定,会话中途不增删、不重排

tools = TOOLS +

```

处在需要用到某工具的情形下, 是由 tool 将完整的内容在后续消息里进行注入, 并非去修改顶层工具数组 `tools` , 此外官方还给出了能够经 API 使用的 tool 能力, 用于把这一层予以简化。

验收之际要盯紧两件事情, 其一, 短桩集合在整个会话的生命周期当中, 是不是字节级别的那种一致;其二, 真正完全加载好的时候, 是不是仅通过消息或者工具结果通道进入的, 并且并没有回头去修改 `tools`。前者是确保前缀, 后者是保障行为。

  1. :复用父会话前缀,提示放在最后一条 user

当上下文快要满的时候, 需要先把长历史送给模型去做摘要。要是另外开启请求, 更换一套「请摘要」, 并且还不带有tools的时候, 前缀从第一个token开始就会与父会话产生分叉, 长历史按照全额未缓存输入来计费。会话的时间越长, 这次「为了省上下文」的调用就会越贵。

Code 的 cache-safe :

  1. 用上跟父会话一模一样的, user/的, tool定义。

  2. 前置父会话 ;

  3. 把 提示**追加为新的 user 消息**;

  4. 预留 ,给摘要提示和摘要输出留 token。

```

= (

" the for . Keep goals, , "

def ():

同一 / TOOLS / ,不要另起「摘要专用」

= list() +

{"role": "user", "": }

turn()

```

就 API 的角度看去, 此次请求差不多等同于「在父会话的上一轮里再多出来一条 user」, 所以能够获取到已有的前缀缓存。官方在后续也将该能力径直做到了 API 当中;在进行自建的时候依旧需要明白这个前缀约束。

  1. 动态信息走 ,不改静态

日期, 以及当前打开的文件, 还有用户刚刚修改的配置, 要是写进静态之中, 那就等同于每一轮都要重写前缀啦。Code的习惯是这样子的: 在于下一条user消息或者里增添标签进行更新操作。静态段要维持着可缓存的状态;变化的部分则落在后缀那里。

同样的情况, 不要于会话进行当中更换模型去继续同一个长前缀。要是非得进行更换, 那么优先选用子Agent, 让当前模型书写一份, 接着开启新的会话。Code的这类子Agent通常会使用更小的模型, 就是基于这样的思路。

常见坑

  1. 断点被钉在所变化的那块之上, 时间戳, 请求ID, 还有本轮用户自己的原文要是恰巧处于断点所在的那块区域内, 那就没办法找到稳定的写入位置。断点, 应该被钉在那「跨请求没有变化」的处于最后的那块。

  2. 工具层处于前缀最靠前 的位置那种情况,中途进行增删或者重排tools,任何细微更改会致使tools//整链失配。Plan Mode以及MCP这二者,都应当避开那个「对tools数组做出改动」的行为。

  3. 针对于被称作静态里塞深度时间戳之物进行相关考量, 在Code复盘这个行为当中, 有特地对这类回归予以点名陈述,具体情况为, 只要是出现一次被形容为「看起来无害」的时间注入这种情况, 便能够达成让全局缓存失效此番结果。

  4. 与之不同的是, 关于空tools的那种摘要调用, 是按照未缓存全量来进行计费的, 而且必须要复用父前缀, 还要把提示放置在末尾的user那里。

  5. 中途通过换模型以达到省钱 , 缓存依照模型进行彼此隔离 , 当十万token级的会话切换至小模型时 , 有可能是需要重新构建全部的前缀 , 而账单却不一定会更低。

  6. 只关注延迟, 而不理会命中率, Code 将缓存命中率当作可用性指标紧盯不放, 一旦这个数值低下便开启 SEV 模式, 自建的 Agent 最少要依会话进行聚合操作, 也就是用( + + input)来处理, 在回归阶段还要予以对比甄别。

  7. **TTL遭误判的情况很特别哎**, 它 默认是按照从发起请求开始计算时长为5分钟, 那种长流式的响应居然是会把窗口给消耗掉的。对于频率非常低但复用时间间隔是以小时来计算的前缀, 那就得再去考虑设置 `ttl:"1h"` 这种情况啦(写入的时候得两份哦)。

  8. **JSON的键序并非保持着稳定性。**具体而言, 存在一些语言, 当对诸如 ``此类结构去实施序列化操作的时候, 那么就会扰乱键的既有排列顺序, 此时跟随发生变化的便是前缀哈希。而要是序列化层肩负起固定键序的职责, 那么在联调之时, 就得选用原始请求体去开展字节层面的对比。

  9. 将``拿来当作总输入。官方给出的说明是, ``主要指的是断点之后的未被缓存的段落。总成本以及限流应当按照`++input`这样去理解, 不然的话就会产生「usage很小然而账单却不小」的那种错觉。

小结

这是前缀匹配的问题, 并非那种差不多相同就能够命中的情况。官方所规定的路径是, 针对管多轮增长, 要钉住异频静态段。计费的标准是, 按照5分钟写1.25×, 1小时写2×, 读通常为0.1×(Fable 5.1 / 5.1的hits在官方表中是0.025×)。与 Code 相关的工程含义更具刚性: 工具的顺序方面存在集合冻结的情况, 动态信息会进行流转, 在 Plan Mode 中运用 Enter/Exit 工具, 于 MCP 里采用 `` 短桩, 复用父会话并结合多工具, 还将把摘要提示附加作为使用者。

落地之际, 使用三条验收线便足矣: 自同一会话第二轮起, `ens` 能否稳定逐步提高;于切换 Plan Mode / 加载 MCP 之际, `tools` 数组是否依旧字节保持一致;所请求的 +tools 与父会话是否相同。将缓存命中率视作与同等级别的指标, 才会由文档参数转变为账单以及 TTFT 上可稳定测得的演进, 最终得以改善。

参考:

相关推荐
李兆龙的博客5 天前
问津集 #17:Terark-DS——KV 分离后的 WAL 写入、冗余策略与 GC
compaction
StarRocks_labs7 天前
StarRocks 存算分离架构下的大规模实时导入优化实践
starrocks·flink·sstable·compaction·tablet·主键索引·存算分离架构
李兆龙的博客7 天前
问津集 #15:RangeReduce——查询驱动 Compaction 收益
compaction
小七-七牛开发者10 天前
拆解 dsh 系列:从源码和版本变化看 DeepSeek Harness 的设计取舍
ai·大模型·claude·token·工作流·skill·claudecode·ai coding
小七-七牛开发者11 天前
Agent 小知识 | Skill 的设计与生命周期:从工具接口到能力模块
ai·大模型·agent·token·工作流·claudecode·ai coding
小七-七牛开发者12 天前
拆解 DeepSeek Harness:Profile 与 Bundle 如何装配运行时
ai·大模型·agent·token·工作流·claudecode·ai coding
小七-七牛开发者13 天前
61 亿次请求背后:LLM Serving 的 Cache 与调度难题
ai·大模型·agent·token·工作流·claudecode·ai coding
Roadinforest14 天前
Claude Code 架构深度解析:从 Agent Loop 到 Tool、MCP 与 Context
ai·架构·llm·agent·anthropic·claudecode
深念Y16 天前
为什么选 MiniClaude:一个精简开源替代方案的选型过程
ai·开源·开源软件·agent·claude·workflow·claudecode