首页
/ caveman 双轨优化体系解析:Prompt 缓存规划器(Cache Planner)与轨迹重写器(Trajectory Rewriter)

caveman 双轨优化体系解析:Prompt 缓存规划器(Cache Planner)与轨迹重写器(Trajectory Rewriter)

2026-09-05 17:37:45作者:齐冠琰

本文以 docs/technical/cache-and-rewriter.md 为主线,深入 caveman 仓库中两套彼此独立、互不替代的上下文优化系统:为请求注入供应商原生提示缓存控制的 cache planner,以及对老旧低价值 agent 轨迹进行受控压缩的 trajectory rewriter。读完后你会理解两套系统各自的安全门槛、fail-safe 语义、证据分离原则,以及从源码层面验证它们行为边界的具体路径,从而在代理系统中正确选型并评估其可验证性。

总览:两套系统,零响应缓存

caveman 的 token 优化不依赖"缓存模型响应"。技术文档开宗明义:

  • cache planner(缓存规划器):在供应商原生提示缓存可用、且经济与请求形态支持的前提下,向请求添加 provider 原生的缓存控制标记(breakpoint / cache hint);
  • trajectory rewriter(轨迹重写器):调用一个 agent 看不到的外部模型,对 agent 转录(transcript)中较老、较低价值的区段做压缩,且输出必须先通过确定性结构检查才被接受。

文档特别强调一句关键事实:"Neither system caches model responses."(两者都不缓存模型响应)。这是理解整个体系产品边界的起点:cache planner 是元数据级请求变换,provider 仍负责跑模型并报告缓存计数器;它与"重放存储输出的响应缓存"或"自托管 KV 缓存"是不同产品、不同正确性边界(见 cacheengine/README.md 的 "Product boundary" 一节)。

两套系统各自落在独立的 Go 包中:cacheengine/(缓存规划器)与 rewriter/(轨迹重写器),均随源码提供,许可为 Business Source License 1.1(见 rewriter/README.mdcacheengine/README.md)。

Cache Planner:本地规划,零网络调用

工作机制

Cache planner 检查一个请求,识别稳定前缀边界(stable prefix boundaries),然后添加 provider 原生的缓存提示。规划与请求优化过程不发起任何网络调用——这一点在 cacheengine/engine.goEngine 结构中可以印证:它持有的是 cacheguard 守卫、前缀安全检查缓存(prefixSafety)、key 分片上限与 resolver/driver 回调,没有任何 HTTP 客户端字段;README 也明确 "Optimize makes no provider call"。

内置 planner 覆盖 Anthropic、OpenAI、Amazon Bedrock 与 Google Gemini 四类 provider;未知 provider 原样透传(pass-through),不做任何字节改动。

内置 provider 的原生行为矩阵

cacheengine/README.md 给出的 native bridge 策略表是实操选型时的核心参考:

Surface Behavior Attribution ceiling
Anthropic 复用既有稳定 tool/system breakpoint;追加滚动式顶层自动缓存(rolling top-level automatic caching) 因果型 provider 观测;standalone 金额保持为零
OpenAI GPT-5.6 家族 作用域 affinity key + 1 个稳定 breakpoint 与最近 3 个显式 breakpoint;无安全可标记块时退化为仅 affinity 因果型 provider 观测;仓库级已验证账本扩展尚未构建
更早的 OpenAI 基于 provider 自动缓存的作用域 affinity key 仅 affinity
Bedrock Anthropic Claude 复用 catalog 门控的稳定点 + 滚动消息 checkpoint 因果型 provider 观测;standalone 金额保持为零
Gemini 观测隐式的 provider 托管缓存,不改写 body 自然发生(organic),永不归属到 engine
Unknown 精确透传(exact pass-through) 不可用

这张表的工程含义是:planner 对不同 provider 的"归因上限"(attribution ceiling)是分档声明的,而非一刀切。也就是说,caveman 不会把所有 provider 的缓存收益都算到自己头上——Gemini 的隐式缓存被标记为 organic、不归属 engine;这正是文档中"provider observations retain their own evidence basis"的底层设计。

Fail-safe cases:planner 何时保持原请求

技术文档 列出 planner 保持原始请求不动(保持字节不变)的七类情形:

  1. record mode(记录模式);
  2. 不支持的计费档位(unsupported billing tier);
  3. 畸形或含糊的 JSON(malformed or ambiguous JSON);
  4. 重复的 JSON key
  5. 不支持的 provider 行为
  6. 候选边界处存在易变内容(volatile content at a candidate boundary);
  7. provider 语义偏离了已注册的能力数据(capability drift)。

源码侧与之对应:cacheengine/README.md 补充了更多触发 fail-safe 的条件——body/metadata 模型不匹配、非 PAYG 模式、前缀漂移(prefix drift)都会保留原始字节;"callers cache fields always win"(调用方自带的缓存字段优先)。而 cacheengine/engine.goNewNewChecked 的构造逻辑体现了 fail-closed 原则:配置错误不会在构造时 panic 逃逸,而是被存储进 configErr,之后每一次 Plan/Optimize 调用都会返回该错误——"legacy New also stores configuration errors and makes every operation fail closed"。

此外,引擎在 engine.go 中定义了资源边界:

const (
    maxConfiguredKeyShards = 1_000_000
    defaultInputByteLimit  = 64 << 20   // 64 MiB 默认
    maxConfiguredByteLimit = 1 << 30    // 1 GiB 上限
)

provider 原生 body 与框架化稳定前缀默认各受 64 MiB 独立限制(可通过 MaxRequestBytes / MaxStablePrefixBytes 配置),且在复制或拼接之前就拒绝超限请求;显式配置的限额必须落在 1 字节到 1 GiB 之间。请求标识、分段名、profile ID 与路由元数据均有长度上限并拒绝控制字符。这些约束保证 planner 在任何路径上都不会"悄悄改坏"请求。

文档还强调一个重要的经济学纪律:planner 使用期望调用次数与 provider 计费单位做通用规划,"does not invent dollar savings when provider pricing evidence is absent"(在缺少 provider 定价证据时不虚构美元节省)。cacheengine/README.md 的通用 planner 示例印证了这一点——EconomicsWriteMultiplier/ReadMultiplier 等输入费率单位表达(如写 1.25 倍、读 0.10 倍),"Economics use input-rate units, never guessed dollars"。

Provider observations:缓存提示不等于缓存命中

这是文档中最容易被误读的一点:一个 cache hint 不能证明发生了 cache hit。是否发生 cache read 或 cache write,由 provider 响应中的 usage 字段决定。本地规划记录始终是推断(inferred),而 provider 观测则保留其独立的证据基础。

源码中对应两个观测入口(见 cacheengine/README.md):

  • Observe:接受归一化后的 provider usage,区分 hit / write / miss / unavailable;
  • ObserveRawCacheUsage:额外映射官方原始缓存计数器,包括 OpenAI 的 cache_write_tokens

两条路径都"never mint verified savings"(不凭空铸造已验证的节省)。也就是说,caveman 在证据链上严格区分三类数字:planner 的本地推断provider 报告的真实 usage下游模型质量——后文"选择系统"一节会说明为什么它们不可互相替代。

通用 planner 与自定义 provider

核心 planner 只认识能力(capabilities),不认识 provider 名字。给它有序的 stable segments 加缓存经济学参数,它会选择正盈亏平衡的前缀点、守卫 epoch 字节、检测易变数据与漂移,并生成 tenant 不透明、按负载分片的 affinity key。最小示例(摘自 cacheengine/README.md):

engine := cacheengine.New(cacheengine.Config{})
plan, err := engine.Plan(cacheengine.PlanRequest{
    Scope:         "org/project",
    Epoch:         "conversation-42",
    ExpectedCalls: 8,
    Profile: cacheengine.Profile{
        ID: "provider-cache-v1", Mode: cacheengine.ModeExplicit,
        MinPrefixTokens: 1024, MaxBreakpoints: 4,
        EconomicsKnown: true,
        WriteMultiplier: 1.25, ReadMultiplier: 0.10,
        RoutingKey: true,
    },
    Segments: []cacheengine.Segment{
        {Name: "tools", Content: toolBytes, Tokens: 1800, Stable: true, Cacheable: true},
        {Name: "live", Content: userBytes, Stable: false},
    },
})

其中 ExpectedCalls 的语义有明确前提:预期在 provider 缓存条目保持温热期间共享该前缀的调用次数——不要喂入跨越缓存过期间隙的终身总调用数。token 数未知时,安全变换仍然可用,但经济学报告为 unavailable。

接入新 provider 只需提供能力 profile 加一个 Driver,planner 本体不变;native profile 必须显式绑定 Provider,Driver 在无法安全编译时必须返回无优化器 ID 的原始字节(fail-safe 的最后一道防线)。

Trajectory Rewriter:受控的"只压缩、不删除"

定位与目标

Trajectory rewriter 瞄准 agent 转录中较老、较低价值的区段(older, lower-value zones)。当前支持显式配置的 Anthropic 与 OpenAI 重写模型——这一点在 rewriter/rewriter.goNew 构造函数中得到验证:Provider 只能是 anthropicopenai,未知 provider 直接报错而非静默走默认路径,"a silently mis-routed rewriter would produce bytes no gate was designed for"。rewriter/provider.go 固定了两个端点:Anthropic 的 /v1/messagesanthropic-version: 2023-06-01)与 OpenAI 的 /v1/chat/completions

该包的头部注释(rewriter/rewriter.go)披露了机制来源:AgentDiet(arXiv 2509.23586),"parameter for parameter" 地移植进 wire proxy 而非 agent loop——一个 agent 永远看不到的外部模型、a=2 步滞后加 b=1 步额外上下文、双 θ=500 门(低于 θ 跳过、节省未过 θ 则不应用)、以及 rewrite-never-delete(被移除的文本用一句短小的 takeaway 替换,而不是直接丢弃)。

重写生效的六个前置条件

技术文档 给出的 rewrite path 六条件,与源码一一对应:

  1. 输入超过配置的阈值,且在任何 provider 调用之前判定。对应 Rewrite 入口

    originalTokens := countTokens(req.StepBytes)
    if originalTokens <= c.theta {
        return Result{RejectReason: reasonBelowTheta}, nil  // 根本不发 API 请求
    }
    

    这是"invocation gate":θ 以下直接跳过,连重写模型的开销都省了。θ 由 Config.Theta 配置,默认 DefaultTheta = 500rewriter/rewriter.go),注释明确指出它是"replay-tunable parameter, not a constant of nature"——正是它让整个机制在小块语料上自我禁用、在仓库级语料上触发,实现语料自适应。

  2. 模型与凭据必须显式配置New 要求 ModelAPIKey 非空,Theta 不得为负(rewriter/rewriter.go)。

  3. 响应必须通过结构性接受检查。这是 Accept 纯函数(见下文"确定性接受门")。

  4. 失败信号与计数必须保留,连同 exit code 和引用。对应 rewriter/gate.go 中的 fidelity 检查族:failure_signalfailure_detailcountexit_codereference

  5. 输出的节省量必须超过配置的大小阈值("output beats configured size threshold")。Accept 中先于 fidelity 检查执行:

    if countTokens(original)-countTokens(rewritten) <= theta {
        return reasonTokens
    }
    

    即"application gate":节省量本身也要过 θ。检查顺序是"shape → saving → fidelity",文档与源码一致。

  6. 原始字节必须存储在恢复指针(recovery pointer)背后。见下文"恢复与信任"一节。

此外还有两个结构性检查(fail-closed 的一部分):完成被输出上限截断(gate_failure_truncated,依据 provider 自己的 stop reason:Anthropic 的 max_tokens 与 OpenAI 的 length,见 rewriter/provider.go)、模型回显 <step> 包装或 markdown 围栏(gate_failure_step_wrapper / gate_failure_fence)。输出上限由 outputCeiling 计算:以原 step 自身 token 数为诚实上限,夹在 minOutputTokens = 256maxOutputTokens = 16384 之间(rewriter/rewriter.go);触顶即拒绝(reasonTruncated),绝不静默截断——因为被裁掉的尾部恰恰是"被保留的失败"所在。

被拒绝的重写永远不会插入 agent 上下文。但注意文档后半句:被拒绝尝试的 provider usage 仍然会被上报,"because spend occurred"(花费已经发生)。源码中 Result.InputTokens/OutputTokens 是重写模型自己的 provider 计量,且在拒绝调用上同样报告,好让"宣称的节省"能与"重写本身的花费"相抵(见 Result 结构注释,rewriter/rewriter.go)。

确定性接受门:比长度更严格的检查

Accept(original, rewritten, theta) 是一个纯函数——无时钟、无网络、无状态(rewriter/gate.go),离线回放网格(replay grid)用同一个函数重新打分捕获的语料,"an offline verdict and a live verdict cannot diverge"。完整拒绝原因枚举(Result.RejectReasonrewriter/rewriter.go):

below_theta / api_error / gate_failure_truncated / gate_failure_step_wrapper / gate_failure_fence / gate_failure_empty / gate_failure_tokens / gate_failure_failure_signal / gate_failure_failure_detail / gate_failure_count / gate_failure_exit_code / gate_failure_reference

fidelity 检查的具体规则(rewriter/gate.go):

  • 失败信号词failureNeedles,大小写不敏感):failerrorexceptiontracebackpanicfatalwarnerr!not foundno such filetimed outtimeoutsegmentationcore dumpedkilledabortedconflictdeniedrefusedunable tocannot 等。原文出现而重写文本缺失任何一个 → 拒绝。注释明确:过匹配只会让门更严。
  • 失败计数必须带数字原样存活countPattern):把 "5 failed" 改成 "1 failed" 即使信号词全在,也属于对问题规模的撒谎;匹配带词边界,防止 "1 failed" 被 "21 failed" 满足。
  • 非零 exit code 必须存活exitStatusPattern):exit code/status (\d+),只保护非零值("exit code 0" 不承载任何需要恢复的失败),且 exit code 1 不能被 exit code 12 满足。
  • 源码位置无条件保护sourceLocationPatterns):path.ext:line[:col]、无扩展名 Makefile:12 形式、tsc/MSVC 的 path.ext(line,col)、CPython traceback 的 File "…", line N——出现在原文任何位置都要字节级存活,不允许意译。注释解释:go build、tsc、gcc、rustc、eslint 都把位置单独成行且该行没有失败信号词,若只在失败行范围内保护会漏掉它们。
  • 失败行上的裸路径与行:列坐标受保护(非失败行上的引用可以省略——省略 ok pkg/foo 0.01s 正是本组件存在的全部意义)。
  • 失败细节行必须逐行存活(忽略周边水平空白):一个通用的 "error" 词不能顶替多个不同的失败;重写文本允许在诊断前加上保留的源码位置(后缀匹配),但更短的错误不能被更长的错误满足。

门的设计哲学写在 gate.go 的注释里:唯一不安全的方向是接受了丢掉一个 agent 仍需处理的失败——那会把 token 节省变成一次静默的错误转向。代价是刻意的:一个 200 条警告的编译器日志会因"源码位置无条件保护"而永久不可翻转(permanently unflippable),源码选择接受这个代价而不是开豁免,"A carve-out is only defensible once the replay grid prices that block class as material."

提示词设计与 PromptVersion

反思提示词(rewriter/prompt.go)复刻 AgentDiet 的四段结构:任务描述、<step id="…"> 包裹的 I/O 格式、三类浪费的示例、防信息丢失的准则。三类浪费(three kinds of waste):

  1. USELESS——从未对任何人重要的内容:构建闲聊、进度条、文件列表里的 __pycache__/.git 条目、依赖解析噪声、重复的 banner 行;
  2. REDUNDANT——窗口他处已存在的内容:编辑器回显刚写入的文本、两步前取过的文件内容、逐文件重复的警告;
  3. EXPIRED——产生时重要、现在已不重要的内容:agent 已把候选缩到一个文件的 grep 完整列表、已经得出结论的环境探测。

准则部分强制"condense, never delete":删除处必须留一句 takeaway(如 "individual test lines omitted; 214 PASSED");所有失败逐字保留;标识符原样保留;agent 尚未处理的内容(未应用的 diff、未读取的输出、未回答的问题)一律保留;无可安全压缩时原样返回——"That is a correct answer; a lossy rewrite is not."

输入窗口渲染(buildUserMessagerewriter/prompt.go)按论文的 s / s-1 / s-2 记法从最新 step 向前编号,良构的 a=2 窗口目标即 "s-2"。一个重要的防御:若 StepBytes 不在 WindowBytes 中,窗口被丢弃、目标单独呈现——调用方不匹配绝不能导致模块改写了交给它的 step 之外的内容。

PromptVersion(当前为 "1"rewriter/prompt.go)承载一个强约定:内容寻址的重写存储必须以 SHA-256(stepBytes) || PromptVersion 为键,绝不能只用 step 哈希——否则编辑过的提示词会持续重放旧版本生成的重写,存储会静默混入两套回放网格分别定价的机制输出。任何对 systemPrompt 或 user-message 渲染的改动都必须 bump 该版本号。

为什么保留失败信号

技术文档 对此有一节专门解释:"Why failures are preserved"——过去的失败往往解释了 agent 为何选择当前路径;删掉一个 exit code、错误类或失败命令,会让后续步骤显得"没有动机"(unmotivated)。因此接受检查远不止检查输出长度。上文 gate.go 的五类 fidelity 检查正是这段论述的实现:每一个检查都是 survival check——证明原文中存在的东西在重写中仍然存在。

包注释还诚实地声明了一个非保证(named non-guarantee):加法式捏造(additive fabrication)不会被捕获。所有门检查都是存活性检查,没有任何约束限制模型添加什么——一句捏造的安抚("the remaining failures are unrelated")会通过所有检查。对此的防线是 live Δturns 上的 harm tripwire,而不是这个门本身(rewriter/rewriter.go)。

恢复与信任:CCR 指针

被接受的重写是模型生成的文本,不是无损编码技术文档 的 "Recovery and trust" 一节指出:CCR 提供了访问原始轨迹的途径,但调用方在宣称"质量等价"之前仍需要做任务级评估。

实现上,被接受的重写末尾自动追加一行恢复指针(rewriter/rewriter.go):

func recoveryPointer(original []byte) string {
    return "\n[condensed by caveman; original recoverable via <<ccr:" + ccr.Handle(original) + ">>]"
}

该标记语法与 proxy 压缩路径发出的 appendCCRMarker 完全相同,并被 retrieve 工具消费;句柄即 ccr.Handle(StepBytes)engine/ccr/store.go 中的 Handle 函数)。这里有一个调用方必须承担的契约

调用方必须在把一个被接受的重写放到 wire 上之前,把原始字节持久化到该句柄背后——即 CCR store 的 retrieve 路径可达的位置。一个句柄解析不到任何东西的标记比没有重写更糟:agent 被告知恢复存在而它并不存在。

由于恢复指针随块同行上 wire,节省量是在实际离线的字节(含指针)上测量的(rewriter/rewriter.go),所以宣称的节省永远不是虚数。

如何在两套系统之间选择

技术文档 的选型表完整继承如下:

Need Use
复用精确稳定的 provider 前缀 Cache planner
缩短旧对话历史 Trajectory rewriter
避免任何模型可见的字节变化 Record mode;仅在请求契约把缓存提示视为元数据时使用 cache hints
离线工作 Cache planner 或 Engine;不是 trajectory rewriter

选型背后的差异可以浓缩为一句话:cache planner 是纯本地、零成本、fail-safe 的元数据变换;trajectory rewriter 是要花真实 token 钱、靠确定性门兜底、且产物需要 CCR 恢复保障的有损压缩

两套系统可以共存,但文档划定了明确的证据边界:"Systems can coexist, but evidence must remain separate."(系统可以共存,但证据必须分离。)四类度量彼此独立、不可混同:

  • cache usage(provider 报告的缓存读写);
  • Engine 的 token 估算(本地推断);
  • rewrite-model 的花费(重写模型自己的 provider usage);
  • 下游模型质量(任务级评估)。

这与 rewriter 侧"Meter honestly"的性质声明互相印证:凡本包用自己的 token 计数器测得的都是 inferred 估计,永远不呈现为 provider 计数(rewriter/rewriter.go)。

基准边界与验证方式

技术文档 的最后一段划定了基准的负空间

Repository cache corpus tests measure safety gates and planner behavior. They do not establish a universal provider savings rate or market ranking. Provider features and prices change; recheck capability data and public documentation before publishing current claims.

即:仓库中的缓存语料测试度量的是安全门与 planner 行为,而不是通用的 provider 节省率或市场排名。cacheengine/README.md 进一步给出验证入口与诚实声明:

  • go test -race ./cacheengine/...go vetBenchmarkOptimizeOpenAIExplicit 基准、go run ./cacheengine/cmd/cache-experiment零 provider 调用;fixture token 计数与盈亏平衡输出是建模证据,不是 live cache-hit 证据);
  • cachebench 在合成与公开 agent trace 上施加严格的 97% request-hit 与 eligible-token-hit 门,覆盖计划压缩、provider 特定 wire 变换、模型可见请求等价性、TTL 失败演练与 provider 观测 JSONL 回放;其回放协议见 cachebench/REPLAY_PROTOCOL.md——"Simulation and provider-observed reports never blend"(仿真报告与 provider 观测报告永不混合);
  • 内置能力行为在 2026-08-10 对照过四家 provider 的官方文档核验,README 同时声明当前公开语料工件是保守仿真,且严格 97% 门尚不通过,"No best-in-market claim exists yet."

对 rewriter 侧,其门函数纯函数化(无时钟/网络/状态)的设计直接服务于这个基准边界:离线回放与 live 判定用同一个 Accept 函数,两者不可能分歧,从而使"安全门"本身成为可回放、可审计的对象。

小结

caveman 的这套双轨优化体系可以概括为四条工程纪律:

  1. 规划本地化:cache planner 不做网络调用、不发明美元节省、未知 provider 精确透传、边界异常一律 fail-safe 保留原始字节(cacheengine/engine.go);
  2. 压缩受控化:trajectory rewriter 双 θ 门(invocation 与 application)+ 五类 fidelity 检查 + 截断即拒,保证"token 节省"永不变成"静默错误转向"(rewriter/gate.go);
  3. 证据分离:推断、provider 观测、重写花费、下游质量是四类不可混同的度量(cacheengine/README.mdrewriter/rewriter.go);
  4. 恢复可达:被接受的重写附带 CCR 恢复指针,原始字节必须先落盘再上线,SHA-256(stepBytes) || PromptVersion 作为内容寻址键(rewriter/prompt.go)。

对需要把长轨迹 agent 接入代理网关的读者,这套体系的参考价值不在于任何单一压缩率数字,而在于它把"何时不动请求""压缩产物凭什么可信""节省数字从哪里来"三个问题都落实到了可回放、可审计的源码与测试上——这正是 docs/technical/cache-and-rewriter.md 所定义的基准边界。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384