01三种把 Agent 放进群聊的方法
过去一年,把 Agent 塞进群聊的做法大致分成三派,它们对“谁有身份、上下文归谁”的回答不同,成本结构也就不同。
第一派把 Agent 做成平台里的成员。Anthropic 的 Claude Tag 把一个 Claude 放进 Slack 频道,全员共用,它以自己的身份行动——自己的服务账号、自己的 GitHub App,不借用任何人的个人凭据;Block 开源的 Buzz 更彻底,底下是一个自托管的通信协议,人发消息、Agent 发消息、CI 跑完发事件,都是同一份日志里的签名记录,所以它的 README 敢写“Agent 是成员,不是机器人”;明略的 Octo 把自己定位成协调层,平台管身份和任务,干活交给 OpenClaw、Claude Code 这些 runtime,用插件挂进来。这一派的上下文按频道隔离:一个频道一套记忆,撤销身份等于全局收回。
第二派把 Agent 做成挂在群里的机器人。飞书、企业微信、钉钉都提供了机器人能力,但机器人更像外挂服务:企业微信里知识集挂在机器人身上(最多一千个文件),会话最多十轮,群聊只支持内部群;换个群,还是同一套知识,但群里的上下文并不会跟着机器人走。Discord 没有第一方 Agent,答案是自己写个 bot 进来,身份、审计、记忆、成本全归你。
第三派是自带底座的 harness。OpenClaw、Happy、xAI 的 Grok Bot、开源侧的 DS Bot 都属于这一类:Agent 跑在自己的 harness 里,用你自己的模型额度,再通过插件接进 IM。DS Bot 的做法很典型——一个主 bot 管一群 bot,每个群有管理员,任何 bot 都能 @ 另一个 bot 把活转交出去,每个成员在每个群里还有独立的隐藏会话。
把这一堆摆在一起,会发现一个被忽略的事实:这三种形态都在解决“怎么把人机混编”,却几乎没有一种在解决“同一段历史怎么只算一次”。而后者才是账单的主要部分。下一节先把账单的形状说清楚。
02账单的形状:钱花在“重读一遍”
一次模型调用被切成两段。前半段叫 prefill,模型把整段输入一次性读进去、算出中间状态,这一段高度并行、吃算力、决定你按下发送后等多久看到第一个字;后半段叫 decode,模型一个字一个字往外吐,算力利用率低,但总时长通常不长。贵的、慢的,几乎都是前面那段。
一个很干净的实测来自 DeepSeek Harness 上的开源插件 DS Bot。有人把本机记账导出来分析:一个叫“办公室主任”的长期 bot 会话,平均每次请求的上下文是 30.8 万 token,而平均输出只有 400 来个 token,输出只占请求量的 0.12% 到 0.16%;峰值那次上下文 51.7 万,输出 508 个。换句话说,这个人花钱养的,几乎全部是“让模型重读一遍前面的话”。
而大家平时优化的方向,恰好不是这一块。今年 10 月的一篇论文 TurnCost 把 600 条公开的 coding agent 轨迹逐条算账,结论很刺人:98.6% 的输入 token 本来就由前缀缓存供给,按“每个 token 全价”估算会把成本高估约 7 倍;而解释“为什么这条轨迹贵”时,回合数解释 84% 的方差,平均每次观测的长度只解释 4%——那些被人反复压缩的大构建日志,其实都落在 99 分位。
还有一层规模上的证据,来自微软今年 10 月的一篇生产环境测量:四条 M365 的 agent 负载、1.21 亿次模型调用。在 Deep Research 这个场景里,89% 的调用是应用自己发起的,中位上下文从第一次调用的 1 万 token 涨到第 30 次调用时的 6.8 万,约 80% 的上下文 token 由前缀缓存供给。他们还算了一笔显存账:单个 p99 会话的 KV 就超过一张 H100 的 80GB;如果把所有活跃会话按峰值留着,需要 501.8 TB 显存,而推理真正占用这些状态的时间只占 5.6%。
这组数字说明一件事:在 Agent 负载里,缓存不是优化项,是成本结构本身。所以问题变成——在数字组织里,这段缓存能不能稳定命中?
03为什么数字组织必然打碎缓存
缓存能不能命中,判定方式比大多数人想的严格:不是“内容差不多”,而是“从第一个 token 开始,一条连续的、字节级一致的前缀”。四家服务商的实现不同,但骨子里都是这一条。vLLM 把缓存切成 16 个 token 一块,每块的键是“父块哈希加本块 token”,所以前面任何一个字节变了,后面所有块都换键;SGLang 用一棵前缀树表达“共享同一段提示然后分叉”,页大小就是一个 token;Anthropic 是显式断点加累积哈希,从断点往回最多比二十块;DeepSeek 最特殊,它的文档明说命中要求“完整匹配一个已持久化的缓存前缀单元”,这些单元只在几处落盘——用户输入末尾、模型输出末尾、公共前缀、以及长输入按固定间隔切出来的位置。
顺着这些规则推,会发现数字组织打碎缓存有五层原因。有意思的是,最容易被讨论的“前缀变了”其实排在第三,排在前两位的是两个跟“变不变”无关的机制:淘汰和放置。
第一层:淘汰。缓存不是被改坏的,是被挤掉的
生产环境里更常见的失败模式是前缀根本没变、只是被挤出了缓存。今年有两篇论文把这件事讲清楚了。一篇(KVFlow)指出,agent 的固定提示本来高度可复用,真正的问题是 LRU 在它即将被再次调用之前把它踢掉了;另一篇(RR-Evict)给这个机制起了个很准的名字——“近因同步”:一个 agent 的历史被访问时,它的一串节点同时被刷新,于是 LRU 的“最近使用”信号在 agent 粒度上退化成“整条轨迹要么全热、要么全冷”,淘汰集中打击少数几个 agent 的私有历史。
这不是理论。RR-Evict 在 SGLang 实现上对比 LRU,把 P99 首字延迟压低了 75.4%、P99 未缓存 token 减少了 65.7%——它没有让缓存变准,只是让淘汰变公平。后果有两个。第一,聚合命中率会骗人:一个 agent 被整体淘汰后回来,必须重算几乎全部累积上下文,产生一个巨大的首字延迟离群值,而与此同时“其他大多数请求仍然保持着可观的缓存复用”——用平均命中率做 KPI,会系统性掩盖多 agent 的长尾。第二,淘汰策略本身值钱:微软那篇生产测量里,当池化预算只有最忙一小时无淘汰需求的十分之一时,LRU 让 63% 的 Deep Research 调用重算上下文,而按“最远下次使用”的规则能降到 31%。
还有一条数字能解释为什么“稳定段前置”这么值钱:有人测过不同类型 token 的复用率,系统提示是 92.3%,思维链 token 只有 2.2%,差 44 倍。稳定段与易变段在一条 prompt 里是串行拼接的,后面的不稳定内容一旦插入,前面稳定内容的位置也保不住——所以“把值钱的放前面”不是排版偏好,是按复用率分配显存。
第二层:放置。同一段前缀被撒到不同的机器上
生产环境不是一个副本。请求被负载均衡撒到十几台机器上,每台各存一份残缺的前缀,命中率照样上不去。这件事有多贵,Preble 那篇给了一组很说明问题的数据:在他们的负载里,一份 prompt 有 85% 到 97% 的 token 与其他 prompt 共享,而当时的部署是“轮询负载均衡加不懂前缀的调度”——于是同一段共享前缀被送到不同的卡上各算一遍。这跟“前缀有没有变”完全无关,纯粹是放置问题。
因果性有人做过干净的实验。CacheRoute 把路由从“不懂缓存的均衡”换成亲和路由,前缀一个字节都没改,命中率从 64.1% 涨到 93.2%;在 60 张 H100 上跑到 176 QPS、p99 首字 3.5 秒。它同时给了一个反例:当亲和能救回的 KV 不够多时(32B 模型那组),收益会被抹平——所以放置不是万能药,得看共享前缀占比。
所以真正的问题不是“要不要粘性路由”,而是“粘性和负载怎么一起优化”。Dynamo 的做法是把路由变成一道代价最小化的题:KV 索引报告每个 worker 上已有的前缀重叠量,在途负载跟踪器报告已经分配的工作,路由器先过滤、再打分。它文档里有一句话很关键——路由是一次放置决策,不是无条件的亲和规则:缓存热的 worker 会因为负载太高而输给缓存冷的 worker。这就解释了为什么“纯粘性”会压死热节点,而“纯轮询”会让命中率归零。
第三层:视角。同一段群聊,对不同成员是不同的字节
这一层最隐蔽。在一个 Agent 自己的会话里,它自己说的话被记成 assistant,别人说的话被记成 user。于是同一段群聊转录,十个 Agent 会编码出十份不同的字节——不是因为内容不同,而是因为“谁说的”这个标签在序列里的位置不同。哪怕消息顺序、文字、标点全部一致,字节也会在第一条 Agent 发言处就分叉。
这一层有一手证据。DS Bot 的 prompt.js 顶部有一行注释,作者写的是“这一段只依赖渲染会话自己的 Bot 记录,所以前缀在多轮之间、成员变化时都保持字节稳定”。作者显然在跟“渲染依赖视角”这件事搏斗。而它的默认失败模式——每个 bot 在每个群里一个独立隐藏会话——正是把同一段群聊编码成 N 份不同前缀。
这一层直接推翻一个流行做法。很多人以为“把稳定的群日志挪到最前面、把角色卡放到后面”就能大幅提高命中率。但如果渲染保留 chat 的角色标签,共享前缀会退到“窗口内第一条 Agent 发言”那里,大约只剩 3,696 个 token(系统 1,500、记忆 2,000、第一条人类消息 200 左右),而不是 53,488。布局优化在账单上的收益恰好是零——它是对的,但它不省你的钱。要真正共享,必须先把转录压成一份与视角无关的规范文本:统一成同一种角色,或者干脆用“工具结果”的形式挂在会话里,而不是每个会话各自渲染一遍。
第四层:布局。共读段在前还是在后
把视角问题解决之后,布局才开始产生收益。同一段 5 万 token 的群消息,四个 Agent 读:角色卡在最前时,每个 Agent 的第 0 个 token 就不同,跨 Agent 复用是零;把共读的群消息放到最前、角色和草稿放到最后,四个 Agent 共用同一段前缀,只有尾巴要各自重算。
第五层:压缩。为省 token 做的优化,往往是成本尖峰的源头
上下文快满时,几乎所有产品都会做压缩:把旧历史摘要成一段,或者换一个新的 part 继续。这两条路都是重写前缀,而重写意味着整条缓存链作废。
DS Bot 那个 issue 里最值得记的一笔,不是崩,而是怎么崩。同一份账本里,上下文四十万到四十五万的那六次请求,命中率连续是 0.0%;而五十一万六千到五十一万八千的那三次,命中率是 99.95%。如果“长度”是原因,这两组不该并存。真正决定命中率的,是那条前缀链有没有断,以及缓存还在不在。
这件事可以算成赔率。在 DeepSeek 上,稳态复用的一 token 值 0.02 元/百万,而需要重算的一 token 值 1 元/百万——一比五十。所以任何压缩动作,只要在 50 个稳态 token 里误伤 1 个,就是亏的。而现有实现(condense、handoff)都是“从第一个 token 起全断”,误伤率是 100%。
同一份 issue 里还有一组更反直觉的数据:命中率 99% 的那几次请求,第一个 token 的等待时间是 18.8 到 28.2 秒;而一次完全没有命中的请求,等第一个 token 只要 9.4 秒。命中率高,不等于快。排队、路由、网关都会叠加进来,命中率只是“低延迟”的必要条件。
把五层放在一起,结论不太舒服:数字组织里绝大部分 prefill 是重复劳动。同一段群聊被 N 个 Agent 各读一遍;缓存被 LRU 挤掉;请求被撒到不同副本;压缩一次 N 份同时作废。而这一切在 token 数量上都看不出来:上下文长度没变,变的是它能被复用的比例。
04十个人加十个 Agent:四种做法,一笔账
把上面四层落到一个具体场景:十个人加十个 Agent 的群聊。这二十个成员的上下文并不一样。
人这一侧其实不产生 token:他们看到的是客户端渲染的完整消息流,人和 Agent 的发言都在,还可以翻历史、看 thread;人自己不调用模型,只是偶尔让某个 Agent 替自己干活。真正进模型的是那十个 Agent,每个 Agent 在自己的会话里有五段东西:系统提示约 1,500 token、角色卡 800、团队记忆 2,000、群消息转录 5 万、本轮私有尾巴 600(草稿、工具结果、与主人的私聊摘要)。合计约 5.49 万 token。
关键的问题是:这五个段里,哪些字节是十个 Agent 共享的?答案取决于设计。于是有四种做法,做成一条链:
| 设计 | 共享前缀 | 命中率(公式 / 计费) | 月成本(DeepSeek) | 相对默认 |
|---|---|---|---|---|
| a 默认:角色在前、各开一个会话 | 1,488 | 2.71% / 71.7% | 174.0 元 | 1.00× |
| b 日志前置:角色卡挪到后面 | 53,488 | 97.43% / 77.9% | 141.4 元 | 0.81× |
| c 单一 scribe 维护规范转录 | 53,488 | 97.43% / 94.3% | 55.2 元 | 0.32× |
| d c 再加轮次冻结与副本亲和 | 53,488 | 97.43% / 96.3% | 44.4 元 | 0.26× |
这张表有三处值得停下来看。
第一,命中率涨了 36 倍,账单只降 3.92 倍。从 a 到 d,公式口径的命中率从 2.71% 涨到 97.43%,看起来是数量级的改善;但账单只从 174 元降到 44.4 元。原因是 a 的自会话缓存本来就已经盖住了 71.7% 的字节——每个 Agent 自己上一次请求的字节是能命中的,私有会话本身就是一条追加式的缓存链。这直接推翻一个流行直觉:把共享前缀做长,并不等于把账单打下来。共享前缀只对“新增字节”和“重写”两件事打折,而这两件事在 a 里只占 28.3%。
由此得到一个可操作的判据:当自会话命中率已经很高时,跨 Agent 共享的边际价值接近于零;只有当会话会被打断——压缩、换段、换副本、换模型——共享前缀才开始值钱。这也解释了为什么 b 只值 1.23 倍:它给了一个本来不缺缓存的人更多缓存。
第二,四档的差距不是并列的,而是一条链。a 到 b 省的是“新增字节”(十份变一份),b 到 c 省的是“压缩重写”(十份变两份),c 到 d 再把压缩对齐到轮次边界、把同群成员钉到同一副本。三列对应三种不同的失效模式,不能互相替代:只做布局、不做写入权收归,省不到三分之一;只做写入权、不做路由,多副本场景下白搭。
第三,推荐 d 是有条件的。单副本下 d 只比 c 好 1.24 倍;它的全部价值押在“你能控制放置”上。把副本数从 1 加到 8,a 会从 174 元涨到 505 元(每个副本各存一份残片),而 d 几乎不变——倍数从 3.9 倍放大到 11.4 倍。所以真正的问题不是“该不该做 d”,而是“你的 harness 有没有资格做 d”。
把 20 万 token 的档案做成稳定只读前缀,上下文比“5 万加每轮 3,000 检索结果插在中段”大 3.7 倍,但每轮账单是 0.1409 元对 0.5147 元——大 3.7 倍,便宜 3.65 倍。因为中段插入把一段 2% 价的稳定内容,变成了一段 100% 价的易变内容。上下文的长度根本不是成本的自变量,“稳定还是易变”才是。
05九个系统,各自在解哪一层
把工程界的解法排一遍会发现,它们其实在解上面四层里不同的层。这一节逐个讲清机制、数字,以及它对数字组织意味着什么。
vLLM:把前缀变成一条块哈希链
vLLM 把 KV 缓存切成固定大小的块,默认一块 16 个 token,每块的键是“父块的哈希、本块的 token、以及额外的哈希”。这条设计只有一个后果,但很要命:块键包含了它前面所有内容的指纹,所以前面任何一个字节变了,后面所有块的键都跟着变。只缓存填满的块,淘汰按 LRU,从空闲队列头部开始踢;多租户下可以注入 cache_salt 防串。
近期版本里多了一个值得注意的字段:prefix_match_unit,允许命中粒度比物理块更细——比如物理块是 1,024 token 的混合架构模型,命中粒度可以设成 32。这是专门为混合注意力模型(块很大、命中粒度粗)准备的补丁。对数字组织的含义很直接:群聊里改一个字、重写一次记忆文件,就会在某个块边界产生新键,其后整条链作废。所以稳定段必须前置,而且必须字节级不变。
SGLang:一棵树,让分叉本身变成共享
SGLang 的 RadixAttention 换了个数据结构:不按块存,把已算过的 KV 存进一棵基数树(压缩前缀树),边上可以挂一串 token,页大小就是一个 token。命中就是一次前缀查找,遇到分歧点就把边切开、生成兄弟节点;淘汰是从叶子往根做的递归 LRU,所以树干最不容易被踢掉。
这棵树刚好就是数字组织的形状:群规、术语表、团队记忆是根,每个成员是枝。论文报的最高吞吐提升是 6.4 倍(对照 vLLM v0.2.5、Guidance v0.1.8、TGI v1.3.0,硬件是单张 A10G 跑 Llama-7B、八张 A10G 跑 Mixtral-8x7B,这是整套系统的端到端口径,不是 RadixAttention 单独的贡献;论文脚注还老实承认,他们对比的是一个“刻意选用的旧版 vLLM”,因为 RadixAttention 当时已经被部分合进新版)。消融里有个细节很关键:ShareGPT 一百个请求的总耗时 74.3 秒,树管理只花 0.2 秒,开销不到 0.3%——所以他们敢默认一直开着。因为没命中的时候它只是多算一遍,不会更慢。
但这棵树有个边界:它只活在单实例内存里,跨副本就断了。这就是为什么后面还要有路由和存储层。
Dynamo 与 Preble:命中率不是缓存问题,是放置问题
NVIDIA Dynamo 的思路是把路由变成一道代价最小化的题:KV 索引报告每个 worker 上已有的前缀重叠量,在途负载跟踪器报告已经分配的工作,路由器先过滤掉不合格的,再对剩下的打分,选一个。它文档里有一句话很关键——路由是一次放置决策,不是无条件的亲和规则:缓存热的 worker 会因为负载太高而输给缓存冷的 worker。这解释了为什么“纯粘性路由”不是终解,也解释了为什么有些请求会忽然变慢。
Preble 把这层权衡写成了一个可判定的不等式(E2 调度):如果“错过的前缀长度”小于“缓存里已有的前缀长度”,就走利用(发给缓存最长匹配的机器),否则走探索(按负载挑)。它报的平均延迟比基线好 1.5 到 14.5 倍、p99 好 2 到 10 倍(Mistral-7B 四张 A6000,Llama-3-70B 八张 H100)。对群聊的含义:共享前缀越长,越该“利用”;越短,越该“探索”。
LMCache 与 Mooncake:让共享前缀“存得住”
这一类常被当成基础设施细节,但它其实是数字组织命题的前提而不是优化项,因为算术摆在那里。
先算每 token 的 KV 有多大:2(K 和 V)乘层数、乘 KV 头数、乘头维、乘每元素字节。Llama-3-8B 是 32 层、8 个 KV 头、头维 128、FP16,合 每 token 128 KiB;70B 是 80 层,合 每 token 320 KiB。一段 30 万 token 的前缀,8B 要 39.3 GB,70B 要 98.3 GB。一张 H100 80GB,扣掉权重和激活,留给 KV 的约 54 GB——只装得下一份多一点点。
再看组织规模。20 人加 20 个 Agent,如果每个成员各有一条独立的长链,就是 40 条:8B 要 1,465 GiB,约 29 张 H100;70B 要 3,662 GiB,约 8 个八卡节点。而如果那 40 条链的头部是共享的(一份 30 万 token 的组织共享前缀加每成员 2 万 token 的私有尾巴),总量降到 335.8 GiB,一个八卡节点装得下——缩了 10.9 倍。
但生产环境不会只有一个副本。四个副本各存一份共享前缀,就要 366 GiB,吃掉一个八卡节点 KV 空间的 79%,剩下 99 GiB 装不下 40 条尾巴需要的 244 GiB。“每个副本各存一份残片”在算术上就是不可行的。分层 KV(LMCache 那种把 KV 抽到显存之外、GPU→内存→本地盘→远端)和 Mooncake 那种把 prefill 与 decode 集群物理分离、用闲置 CPU/DRAM/SSD 拼出 KVCache 池的做法,作用就是让共享前缀在副本之外只存在一次,副本的显存只留“此刻真正热的那一段”。
这套方案也有代价,而且必须算:搬一次 98.3 GB 的 70B 前缀,PCIe 4.0 x16(约 25 GB/s)要 3.9 秒,100Gb RDMA(12.5 GB/s)要 7.9 秒;而“不算、直接重算一遍”按 issue 反推的量级(约 6 万 token/s)30 万 token 约 5 秒。搬运和重算已经是同一个数量级,所以分层缓存必须靠流水线重叠、靠更快的互连、靠“只搬真正需要的那一段”才划算;没有这三件事,它比不做还慢。
CacheBlend 与 DroidSpeak:把可复用对象从“前缀”解放出来
CacheBlend 解的是“被复用的内容不在前缀位置”这件事——RAG、跨成员引用同一份文档,都属此类。它的做法是复用任意位置的预计算 KV,每层只重算 KV 偏差最高的少量 token(5% 到 18%),再按新位置用旋转矩阵修正位置编码。论文报 TTFT 降 2.2 到 3.3 倍、吞吐升 2.8 到 5 倍,质量损失在 0.002 以内。对群聊的含义:需求文档、会议纪要可以存一份,被任意成员插到任意位置。
DroidSpeak 解的是“十个 Agent 跑不同模型”的困境:离线 profile 出这两个模型的关键层,运行时只重算这几层 KV、其余复用,最高 4 倍吞吐、3.1 倍 prefill 加速,质量损失可忽略。它给“组内共享前缀”划了一条边界:模型异构时,共享会退化成“逻辑共享、物理重算”,除非你愿意为每一对模型做一次 profile,而组合成本是平方级的。
Sarathi-Serve:它不提高命中率,它降低“没命中”的伤害
最后这一类是把损害摊平。chunked prefill 把一次巨大的 prefill 切成近等大的块,塞进正在跑的 batch 里,和 decode 交织,形成“无停顿调度”;Mistral-7B 单卡服务容量提升 2.6 倍,Yi-34B 双卡最高 3.7 倍,Falcon-180B 流水线并行最高 5.6 倍。群聊里最难受的不是“慢”,而是“某个人触发了 30 万 token 的重算,于是整个群四十个成员一起等”。它把这种公地悲剧摊成背景噪声,代价是触发者自己的首字延迟变长——这是一个组织层面的公平性取舍。所以更准确的说法是:低命中率的代价,在缺少调度侧对冲时,才会表现为成本和尾延迟同时恶化。
06已经有人在编上下文,但编的不是群聊
上一节讲的九个系统是引擎侧的。我这一轮又把 DS Bot 致谢里点名的十来个项目逐个翻了一遍——它说参考了这些项目的设计思路。翻之前的预期是:开源 harness 都在解“上下文别爆”,没人管“前缀别断”,所以这块空白正是壁垒所在。翻完之后得改口:做得最好的两家已经在做,而且把理由写在注释里,生怕后来的工程师看不见。
OpenCode 的上下文组装入口在 packages/core/src/session/context.ts。它在拼接指令块之前有一段注释,意思是按“从最共享到最不共享”的顺序排,好让基线保持成一段可复用的 prompt-cache 前缀:用户级的目录与指引在前,项目指令居中,日期和环境这种每天、每个目录都会变的东西放最后。代码里 Instructions.combine() 的顺序就是这个顺序——工具目录、MCP 指令、references、skills、discovery、builtins、entries。这就是我在文章里说的“上下文编译器”,只不过人家把判断写成了调用顺序。
Codex 的 codex-rs/core/src/compact.rs 更极端。压缩时如果又撞上上下文超限,它的处理是“从开头修剪以保住缓存(基于前缀),并保持近期消息完整”——这句话出现在一条错误重试的热路径上,不是在文档里。同一份源码的 usage 结构里还维护着 cached_input_tokens 和 cache_write_input_tokens 两个字段,把命中当一等公民记账。
pi-mono 提供了第三种设计:压缩时保留约两万 token 的近期内容(keepRecentTokens: 20000),切点如果落在某一轮中间,就为这一轮单独生成一个 turn prefix summary,而不是把整轮切碎;摘要请求还显式带 cacheRetention: "none",理由是别为一次性的摘要去写缓存。goose 走结构化路线,有独立的 goose-context-management crate,压缩阈值 0.8,摘要里保留文件活动(FileActivity),并把压缩能力抽成 CompactionInput 与 CompactionOutput 两个 trait 交给宿主实现。
另外三家干脆绕开压缩。obelisk 把本机各个 harness 的会话、子 Agent、工作流都索引进一个 SQLite,Agent 自己写 JS 查询去翻(overview()、sessions()、search()、sql()),等于把历史从上下文里搬到数据库;OpenMausBot 把“轮次”做成 HTTP 与 SSE 上的一等对象,配审批与中断;Hermes Bot Mode 更直接,一组有名字的 Agent 组成花名册,每个成员跟主人各自一条会话——DS Bot 的“每成员每群一个隐藏会话”就是从这条思路上来的。
把这些项目摆在一起,会发现它们有一个共同前提:一个用户、一条会话、上下文由一个进程单调地往后追加。OpenCode 排的那个“共享度”,排的是静态指令块——工具目录、项目指令、skills、参考文件,而这些东西对每个用户恰好字节一致,所以共享是免费的。Codex 那套修剪之所以有意义,也是因为会话只有一条,只要保住前缀,后面的增量就都便宜。
数字组织要解的恰恰是右边这一栏。跨会话共享动态对话历史——群聊里会话从一条变成 N 条,需要共享的不再是指令块,而是同一段群聊转录;而转录因为视角(自己说的话记成 assistant、别人说的话记成 user)在每个会话里编码不同。所以准确的表述是:静态指令段的跨会话共享已经解了,动态对话历史的跨会话共享还没人解,中间差着一道“视角规范化”的工序。
这句话把壁垒放到了一个更准的位置。它不是说别人笨,而是说这两件事看着像同一类问题——都是“让缓存多命中”——共享对象的性质却相反:一个是编译期就固定的常量,一个是运行期还在生长、且每个观察者看到的字节都不同的流水。常量可以排好序共享,流水必须先规范化才能共享。而规范化没有现成的库,它要求你对组织里谁在场、谁说的话以什么身份落进谁的会话有完整的控制权——这恰好是 Harness 的职责范围,也是它最难被替换的部分。
07压缩、腐烂与可还原的上下文
上一节讲的是“怎么让复用得更容易”。这一节讲反面:为什么“省上下文”的努力经常反噬。
第一件事是赔率,前面算过:在 DeepSeek 上压缩的赔率是 1 比 50,任何压缩只要在 50 个稳态 token 里误伤 1 个就亏。第二件事是压缩会伤害“后来的问题”。今年 10 月的一篇论文专门测这个:为“当前这个问题”去挑句子,在当题上能涨 11.4 分;但对后面接着问的问题,所有按当前问题挑句子的方法,都不比随机删句子更好;反而是完全不看问题、只按信息量删词的 LLMLingua-2,在后续问题上比随机删句高 12.5 分。群聊的价值几乎全在后续追问上,这条几乎是致命的。
第三件事是上下文腐烂。塞进去不等于读到:Lost in the Middle 里,把相关文档放在最前能到 75.7%,放到正中掉到 53.8%,而完全不给文档、闭卷作答是 56.1%——塞错位置,比不塞还差。后来的系统评测用十八个模型固定任务难度、只变输入长度,结论是性能一律随长度下降;更难的基准里,十三个宣称支持十二万八千上下文的模型,在三万二千 token 时就有十一个掉到短上下文基线的一半以下。
所以正确的方向不是“把上下文压得更短”,而是两件事:把不该进上下文的东西留在外面,只把需要的那一小段取进来(记忆与检索路线);以及把压缩做成可还原的动作。OpenHands 的做法值得抄:会话历史是一个只能追加的事件日志,“忘记”不是删掉,而是往日志里写一条墓碑事件,再由投影层算出“现在对模型可见的事件集合”。这样历史永远完整,上下文只是历史的一个投影——可审计,也可压缩。今年 10 月还有一篇 Cache Engineering for Agents 把这条路推得更远:它指出前缀匹配本身不够,因为相同 token 序列在不同缓存操作序列下会得到不同状态,所以应该让 Agent 直接操作活跃 KV(把选中的缓存从未来注意力里排除、调整保留 key 的位置编码),而不是改写轨迹。
08模型侧:把“长度相关的成本”搬走
模型侧这两年在做同一件事,但搬走的东西不一样。第一种是把 KV 做小:DeepSeek 的 MLA 把 KV 压成潜向量,官方数字是减少 93.3%、最大生成吞吐提升 5.76 倍——等价于把缓存容量这个分母做大。第二种是把注意力做稀:MiniMax 的 MSA 用一个极轻的索引器给 KV 块打分,每个查询只对选中的十六块(每块一百二十八个 token,合两千零四十八个 token 预算)做精确注意力,百万上下文下注意力 FLOPs 降 28.4 倍、prefill 加速 14.2 倍;DeepSeek 那边的 DSA 是同一思路,官方用“API 降价 50% 以上”量化收益。第三种是把状态做成定长:Qwen3.8-Flash-Next 每四层只留一层全注意力,其余用 Gated DeltaNet 的固定大小状态,一百二十五 B 总参数只激活 6B。
第四种是把决策下沉。今年 9 月 TypeSafe AI 发布的 Jev 不生成自然语言,只输出带概率的结构化决策;一次调用大约只占大模型调用 28% 的时间和 2.6% 的价格。但同一批研究也泼了冷水:孤立问题上最准的决策模型,实际接管 agent 任务只解决了 69.5%;不拟合置信度阈值就把工具选择外包出去,成功率掉 19 个百分点。这不是能力问题,是校准问题。
这里有一个我觉得最值得写下来的张力:架构在遗忘,而缓存在记住。全注意力的 KV 是按 token 可寻址的,可以回到任意前缀边界复用;线性注意力的循环状态没法回滚到任意位置,只能存检查点,于是前缀复用退化成“必须在检查点边界上”。今年八九月有两篇论文专门解这件事:既然线性注意力本身带衰减、旧输入的贡献本来就在淡出,那就别每个边界都存检查点,只重放一小段最近的后缀来近似出状态。这个思路说明模型架构和缓存机制正在重新对齐——不是谁取代谁,是互相适配。
所以“模型变强了,缓存就不重要了”这个推断是错的。更准确的说法是:模型侧会把单位成本压低,但不会自动修复命中率;而只要组织里有 N 个成员在读同一段历史,N 份不共享的前缀就仍然存在。Jevons 那条老规律在这里也成立:单位成本降了,大家会把更多东西塞进上下文,总账单未必降——那个 51.7 万 token 的会话本身就是便宜之后的产物。
09三个判断
第一,成本竞争已经从“单价”转到“命中率”,而 Harness 是那个定价的人。今年 9 月有一篇 Harness Tokenomics 把这件事说得很透:Harness 决定用哪个模型来答、决定模型读到什么、决定 prompt cache 怎么用、决定跑哪些子 Agent,所以它既挑了价目表上的费率,也决定了按这个费率买多少量。企业沿用一套没有调过的 Harness,就等于继承它的这些选择和账单。
第二,数字组织的第一性指标是“视角无关的共享前缀”,而不是“上下文长度”。翻完开源 harness 之后这个判断可以写得更准:静态指令段的跨会话共享已经有人解了,动态对话历史的跨会话共享还没人解。两者看着像同一类问题,共享对象的性质却相反——一个是编译期固定的常量,一个是运行期还在生长、且每个观察者看到的字节都不同的流水。常量可以排好序共享,流水必须先规范化。谁能把转录做成一份与视角无关的规范文本,谁就把 N 份前缀变成一份;谁做不到,后面所有优化都只是在给一个漏水的桶打折。而且这件事的 KPI 不该是平均命中率,是尾部:一个 Agent 被整体淘汰后回来要重算几十万 token,它的痛会被另外九个 Agent 的良好命中率平均掉。这也是为什么 DS Bot 的作者会在代码注释里专门写“前缀保持字节稳定”——那不是洁癖,那是成本。
第三,未来十二个月的壁垒是“上下文编译器”。开源世界已经把前半截做出来了——把静态段排成可复用的前缀、压缩时别把前缀砍断、把历史搬进可查询的索引;后半截还空着:把动态转录规范化,让 N 个会话读同一份字节。它要干四件事:把组织里的对话习惯编译成稳定的分段模板(哪一段冻结、哪一段追加、断点打在哪);把转录规范化,做到视角无关;在放置上做对(缓存感知的路由与会话亲和,而不是随机撒);把压缩做成可还原、可预热、不重写前缀的动作。模型侧的稀疏化、混合注意力、弹性部署会继续降低单位成本,但不会消灭这门生意——只是把缓存的形态从“一大段 KV”变成“一小段 KV 加一大段固定状态”。到那时,决定成本的仍然是同一个问题:谁负责维护那条共同的前缀。
成本与负载:TurnCost, arXiv:2610.15093;Understanding KV Residency in Production-Scale Agentic LLM Workloads, arXiv:2610.22863;Harness Tokenomics, arXiv:2609.28919;Cache Engineering for Agents, arXiv:2610.28577;Evaluating Context Compaction on the Questions That Come Later, arXiv:2610.27301
系统与调度:vLLM / PagedAttention, arXiv:2309.06180;SGLang / RadixAttention, arXiv:2312.07104;KVFlow, arXiv:2507.07400;RR-Evict, arXiv:2609.32278;SAECache, arXiv:2605.18825;CacheRoute, arXiv:2608.19677;LMCache, arXiv:2510.09665;Mooncake, arXiv:2407.00079;Preble, arXiv:2407.00023;CacheBlend, arXiv:2405.16444;DroidSpeak, arXiv:2411.02820;Sarathi-Serve, arXiv:2403.02310;KVCompass, arXiv:2610.24179
模型侧:DeepSeek-V2 / MLA, arXiv:2405.04434;MiniMax Sparse Attention, arXiv:2606.13392;Qwen3.8-Flash-Next, arXiv:2608.30320;Nemotron Elastic, arXiv:2511.16664;Tail-Replay, arXiv:2608.30310;SuffixReplay, arXiv:2609.33477;JevAgentHarness, arXiv:2610.21412
长上下文:Lost in the Middle, arXiv:2307.03172;Chroma Context Rot(2025-07);NoLiMa, arXiv:2502.05167
一手材料:GitHub Fei2411/DeepSeek-Bot README 致谢、issue #337 与
prompt.js 源码注释;sst/opencode packages/core/src/session/context.ts(指令块按共享度排序);openai/codex codex-rs/core/src/compact.rs(压缩保前缀、缓存用量字段);badlogic/pi-mono compaction 模块(保留窗口与 turn prefix summary);block/goose crates/goose-context-management(阈值 0.8、结构化摘要);tommy0103/obelisk、milind-soni/OpenMausBot、NousResearch/Hermes-Bot-Mode 的 README;DeepSeek Models & Pricing 与 Context Caching 官方文档;Anthropic Prompt caching 定价;OpenAI Pricing;NVIDIA Dynamo KV-aware routing 文档。均为 2026-10-21 抓取。本图与算例:十人十 Agent 的四种设计、容量核算与赔率计算,由本文的推导脚本按上述假设逐项生成;所有价格为 2026-10-21 口径,假设与不确定度在正文与脚注中标明。