> ## Documentation Index
> Fetch the complete documentation index at: https://agent.minimaxi.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# MCode 工程笔记 05｜怎么让 Agent 知道当前时间

> 模型没有时钟。最直接的做法是往系统提示词里注入时间，但时间每轮都变会拖垮前缀缓存。由浅入深：为什么前缀这么重要，以及 MCode 现在怎么做。

> [!NOTE]
>
> 先想一个问题：Agent 怎么知道当前日期和时间？

顺着这个问题往下推，会碰到前缀缓存——我们按 MiniMax Code（下文简称 MCode）里的实际思路，由浅入深讲。

前几篇一路在给 context 做减法。01 管 subagent，02 挪工具定义，03 管长历史，04 说别让 agent 干等一条 bash、把提示词缓存等过期。空间、时间、停顿税都算过了。这篇换个切面：**就算请求连着发，只要时间塞错了地方，前缀缓存照样废掉。**

![怎么让 Agent 知道当前时间](https://mintcdn.com/agent-cn/GdDz-Wa0oRxI53qY/images/techblog/mcode-notes-05-prefix-cache/cover.png?fit=max\&auto=format\&n=GdDz-Wa0oRxI53qY\&q=85\&s=9e949aacecf82c18912e9752b34392b0)

## 问题：模型没有时钟

模型训练完就没有实时时钟。「最近一周的提交」「今天的日程」「这个文件是昨天下午改的吗」——这些判断都依赖「现在是什么时候」。

不告诉它时间，它会默认自己活在训练数据截止的那一年。告诉它，又怎么告诉？

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

最直观的答案：在系统提示词（SP）里加一行当前时间，精确到秒。

理由很充分。一行字，几十个 token，和整段系统提示词、几千 token 的工具定义比起来，几乎可以忽略。工作目录、平台、shell 本来就在环境信息里，再加一行 `date` 看起来也很顺。

很多 harness 的第一版，都会走这条路。我们也差点——或者曾经——这么想。

## 它怎么坏：时间一变，前缀整段作废

问题在于：**时间每轮都在变**，有时甚至每秒都在变。

系统提示词在请求最前面。时间写在那儿，等于每一轮都重写前缀的开头。后面的工具定义、历史消息，哪怕一个字没动，也全部作废——缓存按前缀匹配，前面一个字节变了，后面就全部 miss。

公开旁证：有人发现 Claude Code 有段时间在系统提示词里塞了一个每次请求都不同的字段，结果[第三方服务商的缓存命中率掉到接近 0](https://x.com/stevencheng/status/2055922152562696197)。主线不是别人翻了车，而是**我们自己也会走的那条直觉路径**，结构一模一样：一个会变的小字段，放在了前缀开头。

一行看起来很便宜的 `date`，乘上轮数和请求数之后，真正贵的是它弄坏的那一整段前缀。

## 那为什么前缀这么重要？

![图 1：真正贵的不是 token 数，是位置](https://mintcdn.com/agent-cn/GdDz-Wa0oRxI53qY/images/techblog/mcode-notes-05-prefix-cache/fig1.png?fit=max\&auto=format\&n=GdDz-Wa0oRxI53qY\&q=85\&s=324398f2e939bb7f822d84faa634c822)

上一篇讲后台任务的时候，碰到的是缓存**寿命**：前台干等太久，缓存过期。这篇是另一个切面——缓存**命中**：塞错了地方，哪怕请求连着发，照样打回全价。

这里只用一条结论：

**缓存按前缀匹配。前面有一个字节变了，后面就全部作废。**

于是同一段内容，放的位置不同，代价可以天差地别：

1. **放在最前面、每次都变的东西，代价最大。** 它会让后面所有内容都没法命中缓存。精确到秒的时间如果塞进系统提示词，每一轮都让整段前缀失效——系统提示词、工具定义、历史消息，全跟着重算。
2. **写进历史、写完不再改的东西，影响有限。** 第 5 轮写进去的内容，到第 6 轮已经是前缀的一部分，只要不被改写，后面一直能命中。代价主要是首次写入，以及 context 一轮轮变长——这正是 03 要管的那头。
3. **只挂在本次请求末尾、不进历史的东西，永远不会被缓存。** 它不弄坏别人的缓存，但每发一次就是一份全价输入。

这是整篇最反直觉的地方：

**一个几十 token 的字段，放错了位置，可能比几千 token 的工具定义还贵。**

工具定义再长，只要每轮字节不变，它就稳稳待在可缓存的前缀里，几乎是「买一次、后面按缓存价用」。反过来，前缀上一个每轮都变的小字段，会把这整段便宜货一次性打回全价。

由此能推出几条很朴素的规矩：

* **不变的放前面，会变的往后放。** 当前时间不进系统提示词，而是跟着每轮用户消息走。
* **写进历史的东西不回头改。** 想调整内容，只改以后的新轮次。
* **精度够用就行。** 精确到秒的时间对模型几乎没用，却保证了每一份都和上一份不一样。

> [!TIP]
>
> \*\*如果你也在做 agent harness：\*\*给每段注入内容标两个属性，「多久变一次」和「放在哪」。变得快又放得靠前的，优先处理。前缀缓存不是省钱彩蛋，是一等公民的设计约束。

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

![图 2：一次「hi」里，哪些是稳定前缀](https://mintcdn.com/agent-cn/GdDz-Wa0oRxI53qY/images/techblog/mcode-notes-05-prefix-cache/fig2.png?fit=max\&auto=format\&n=GdDz-Wa0oRxI53qY\&q=85\&s=ee4b333444fe129bbf00bfe66da2842a)

规矩有了，再回头看 MCode 的请求长什么样，后面的做法会清楚很多：

| 内容 | 放在哪 | 多久变一次 |
| - | - | - |
| 核心系统提示词（身份、行为规则） | 请求最前面 | 基本不变 |
| 环境信息（工作目录、平台、shell、模型、语言） | 系统提示词里 | 一次会话内不变 |
| Skill 目录（每个 skill 一两行说明） | 系统提示词里 | 装卸 skill 时才变 |
| 工具定义（每个工具的 JSON Schema） | 请求的 tools 字段 | 基本不变 |
| 历史消息 | 中间 | 只往后追加 |
| 每轮的 `<agent-context>`（身份、会话 ID、当前时间等） | 每轮用户消息的开头 | 每轮都写一份新的 |
| 各种系统提醒 | 用户消息里，或挂在请求末尾 | 按条件触发 |
| 你这一轮说的话 | 最后 | 每轮都变 |

## MCode 现在怎么做

![图 3：同一行时间，放哪儿决定前缀活不活](https://mintcdn.com/agent-cn/GdDz-Wa0oRxI53qY/images/techblog/mcode-notes-05-prefix-cache/fig3.png?fit=max\&auto=format\&n=GdDz-Wa0oRxI53qY\&q=85\&s=11bff15140b2be9afe8ef686f7d0303d)

把上面那条直觉路径反过来，就是我们现在的做法。

**系统提示词里的 `# Environment`，只放会话内不变的东西。** 工作目录、是不是 git 仓库、平台、shell、OS、模型、语言、region、数据目录——这些一次会话里基本定死，所以放得靠前，稳稳待在可缓存的前缀里。这里没有当前时间。公开代码里对应 `environment-prompt-composer` 一类组装逻辑。

**当前时间写进每轮用户消息开头的 `<agent-context>`。** 由 `buildSlimAgentContextBlock` 生成，从第 2 轮起大致长这样（示意，字段值是我们编的）：

```
<agent-context>
  agent: MiniMax Code
  agentName: default
  SESSION ROLE: root
  YOUR SESSION ID: <会话 ID>
  date: Fri Oct 09 2026 13:20 GMT+0800 (China Standard Time)
</agent-context>
```

按上一节的分类，这是第 2 种：写进历史、写完不再改。第 5 轮写进去的时间，到第 6 轮已经是前缀的一部分；只要不回头改，后面一直能命中。模型要的「今天」「最近」仍然有，但不会每轮都去碰系统提示词。

同一行时间，放在前缀上是灾难，放在每轮用户消息里只影响这一轮的尾巴——图 3 把这两种放法并在一起看。

位置对了之后，再顺着同一条线收紧：

* **精度够用就行。** [#459](https://github.com/MiniMax-AI/minimax-code/pull/459) 把 `date` 从精确到秒改成精确到分钟（`formatReminderDate`）。时区和偏移量还在；秒数每轮都变，对模型几乎没用。位置对了，再把「毫无必要的每轮必变」削掉。
* **写进历史的东西不回头改。** 想调整，只改以后的新轮次。已经写进去的 `<agent-context>` 字节不变，前面攒下来的前缀才站得住。

> [!TIP]
>
> \*\*如果你也在做 agent harness：\*\*会变的字段，先问该不该进系统提示词。进了，就等于拿整段前缀去换一行方便。先挪位置，再谈精度。

## 回头看

![图 4：同一条路上的五个路口](https://mintcdn.com/agent-cn/GdDz-Wa0oRxI53qY/images/techblog/mcode-notes-05-prefix-cache/fig4.png?fit=max\&auto=format\&n=GdDz-Wa0oRxI53qY\&q=85\&s=2441c200f1404b181dad67e39fcb697c)

把前几篇放在一起看，其实是同一条路上的几个路口：

* 01：别靠堆 subagent 来管 context
* 02：工具定义按需加载，别全塞进每一轮
* 03：长历史该压就压，要紧的落到 memory
* 04：别让模型干等，把「停顿税」压下去——护的是缓存的**寿命**
* 这篇：别让一行时间拖垮前缀——护的是缓存的**命中**

03 说过：context window 是工作台，不是仓库。这篇想补一句：工作台上每天都要先摆好的那些东西，**摆在哪儿，比摆了多少更要紧**。

怎么让 Agent 知道当前时间？最直接的做法是往 SP 里塞；真正站得住的做法，是让时间跟着每轮用户消息走，护住前面那整段稳定前缀。

## 给做 agent 的几条自查

| 检查项 | 自问 |
| - | - |
| 前缀意识 | 你能指出一次请求里，哪些是稳定前缀、哪些每轮会变吗？ |
| 必要性 | 每一项删掉之后，模型会在哪儿犯错？说不出来的，先考虑删。 |
| 位置 | 当前时间（以及其他会变的内容）有没有放在系统提示词里？有的话，挪到每轮用户消息里。 |
| 精度 | 时间要精确到秒吗？精度越高，越容易每轮都不一样。 |
| 不改历史 | 已经写进历史的 `<agent-context>` 或提醒，有没有在后续轮次被改写？ |
| 补回机制 | 砍掉的信息，在 compaction、恢复会话、子 agent 这些路径上会不会彻底消失？谁负责补回来？ |
| 度量 | 改动前后，有没有同时看缓存命中率、单轮成本和效果？只看 token 数是不够的。 |

这几个改动都在 [MiniMax Code 的公开仓库](https://github.com/MiniMax-AI/minimax-code)里，欢迎来看代码、提 issue、拍砖。

> [!TIP]
>
> 也想听听大家：你们是怎么给 Agent 报当前时间的？有没有被一行 `date` 拖垮过前缀缓存？
