Skip to main content
先想一个问题:Agent 怎么知道当前日期和时间?
顺着这个问题往下推,会碰到前缀缓存——我们按 MiniMax Code(下文简称 MCode)里的实际思路,由浅入深讲。 前几篇一路在给 context 做减法。01 管 subagent,02 挪工具定义,03 管长历史,04 说别让 agent 干等一条 bash、把提示词缓存等过期。空间、时间、停顿税都算过了。这篇换个切面:就算请求连着发,只要时间塞错了地方,前缀缓存照样废掉。 怎么让 Agent 知道当前时间

问题:模型没有时钟

模型训练完就没有实时时钟。「最近一周的提交」「今天的日程」「这个文件是昨天下午改的吗」——这些判断都依赖「现在是什么时候」。 不告诉它时间,它会默认自己活在训练数据截止的那一年。告诉它,又怎么告诉?

最直接的做法:往系统提示词里注入时间

最直观的答案:在系统提示词(SP)里加一行当前时间,精确到秒。 理由很充分。一行字,几十个 token,和整段系统提示词、几千 token 的工具定义比起来,几乎可以忽略。工作目录、平台、shell 本来就在环境信息里,再加一行 date 看起来也很顺。 很多 harness 的第一版,都会走这条路。我们也差点——或者曾经——这么想。

它怎么坏:时间一变,前缀整段作废

问题在于:时间每轮都在变,有时甚至每秒都在变。 系统提示词在请求最前面。时间写在那儿,等于每一轮都重写前缀的开头。后面的工具定义、历史消息,哪怕一个字没动,也全部作废——缓存按前缀匹配,前面一个字节变了,后面就全部 miss。 公开旁证:有人发现 Claude Code 有段时间在系统提示词里塞了一个每次请求都不同的字段,结果第三方服务商的缓存命中率掉到接近 0。主线不是别人翻了车,而是我们自己也会走的那条直觉路径,结构一模一样:一个会变的小字段,放在了前缀开头。 一行看起来很便宜的 date,乘上轮数和请求数之后,真正贵的是它弄坏的那一整段前缀。

那为什么前缀这么重要?

图 1:真正贵的不是 token 数,是位置 上一篇讲后台任务的时候,碰到的是缓存寿命:前台干等太久,缓存过期。这篇是另一个切面——缓存命中:塞错了地方,哪怕请求连着发,照样打回全价。 这里只用一条结论: 缓存按前缀匹配。前面有一个字节变了,后面就全部作废。 于是同一段内容,放的位置不同,代价可以天差地别:
  1. 放在最前面、每次都变的东西,代价最大。 它会让后面所有内容都没法命中缓存。精确到秒的时间如果塞进系统提示词,每一轮都让整段前缀失效——系统提示词、工具定义、历史消息,全跟着重算。
  2. 写进历史、写完不再改的东西,影响有限。 第 5 轮写进去的内容,到第 6 轮已经是前缀的一部分,只要不被改写,后面一直能命中。代价主要是首次写入,以及 context 一轮轮变长——这正是 03 要管的那头。
  3. 只挂在本次请求末尾、不进历史的东西,永远不会被缓存。 它不弄坏别人的缓存,但每发一次就是一份全价输入。
这是整篇最反直觉的地方: 一个几十 token 的字段,放错了位置,可能比几千 token 的工具定义还贵。 工具定义再长,只要每轮字节不变,它就稳稳待在可缓存的前缀里,几乎是「买一次、后面按缓存价用」。反过来,前缀上一个每轮都变的小字段,会把这整段便宜货一次性打回全价。 由此能推出几条很朴素的规矩:
  • 不变的放前面,会变的往后放。 当前时间不进系统提示词,而是跟着每轮用户消息走。
  • 写进历史的东西不回头改。 想调整内容,只改以后的新轮次。
  • 精度够用就行。 精确到秒的时间对模型几乎没用,却保证了每一份都和上一份不一样。
**如果你也在做 agent harness:**给每段注入内容标两个属性,「多久变一次」和「放在哪」。变得快又放得靠前的,优先处理。前缀缓存不是省钱彩蛋,是一等公民的设计约束。

先看一眼:一次请求里装了什么

图 2:一次「hi」里,哪些是稳定前缀 规矩有了,再回头看 MCode 的请求长什么样,后面的做法会清楚很多:

MCode 现在怎么做

图 3:同一行时间,放哪儿决定前缀活不活 把上面那条直觉路径反过来,就是我们现在的做法。 系统提示词里的 # Environment,只放会话内不变的东西。 工作目录、是不是 git 仓库、平台、shell、OS、模型、语言、region、数据目录——这些一次会话里基本定死,所以放得靠前,稳稳待在可缓存的前缀里。这里没有当前时间。公开代码里对应 environment-prompt-composer 一类组装逻辑。 当前时间写进每轮用户消息开头的 <agent-context>。 由 buildSlimAgentContextBlock 生成,从第 2 轮起大致长这样(示意,字段值是我们编的):
按上一节的分类,这是第 2 种:写进历史、写完不再改。第 5 轮写进去的时间,到第 6 轮已经是前缀的一部分;只要不回头改,后面一直能命中。模型要的「今天」「最近」仍然有,但不会每轮都去碰系统提示词。 同一行时间,放在前缀上是灾难,放在每轮用户消息里只影响这一轮的尾巴——图 3 把这两种放法并在一起看。 位置对了之后,再顺着同一条线收紧:
  • 精度够用就行。 #459 把 date 从精确到秒改成精确到分钟(formatReminderDate)。时区和偏移量还在;秒数每轮都变,对模型几乎没用。位置对了,再把「毫无必要的每轮必变」削掉。
  • 写进历史的东西不回头改。 想调整,只改以后的新轮次。已经写进去的 <agent-context> 字节不变,前面攒下来的前缀才站得住。
**如果你也在做 agent harness:**会变的字段,先问该不该进系统提示词。进了,就等于拿整段前缀去换一行方便。先挪位置,再谈精度。

回头看

图 4:同一条路上的五个路口 把前几篇放在一起看,其实是同一条路上的几个路口:
  • 01:别靠堆 subagent 来管 context
  • 02:工具定义按需加载,别全塞进每一轮
  • 03:长历史该压就压,要紧的落到 memory
  • 04:别让模型干等,把「停顿税」压下去——护的是缓存的寿命
  • 这篇:别让一行时间拖垮前缀——护的是缓存的命中
03 说过:context window 是工作台,不是仓库。这篇想补一句:工作台上每天都要先摆好的那些东西,摆在哪儿,比摆了多少更要紧。 怎么让 Agent 知道当前时间?最直接的做法是往 SP 里塞;真正站得住的做法,是让时间跟着每轮用户消息走,护住前面那整段稳定前缀。

给做 agent 的几条自查

这几个改动都在 MiniMax Code 的公开仓库里,欢迎来看代码、提 issue、拍砖。
也想听听大家:你们是怎么给 Agent 报当前时间的?有没有被一行 date 拖垮过前缀缓存?