来源:
https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages
1. 背景
对于 cc 拼接的完整 prompt,系统指令(system message)通常放在最前面,即在所有对话消息之前,是整个 prompt 的开头。
而提示词缓存命中的前提是,请求的前缀必须和之前的请求逐字节完全一致。
所以在传统方案里,一旦需要再对话中途修改系统指令,则会导致缓存完全 miss。
2. 方案
做法很简单:不再去改开头的 system 字段,而是在 messages 数组的当前位置追加一条 {"role": "system"} 消息。
{ "role": "user", "content": "现在审查调用 process() 的代码。" },
{ "role": "system", "content": "从现在起,每条建议都必须包含显式类型标注。" }
关键点:为什么非要用 system 角色,而不是塞进 user 消息? 区别在于优先级。
- user 消息 = 来自终端用户
- system 消息 = 来自(Agent / 应用运营方)
当两者冲突时,系统级别优先。所以对于那些“即使用户提出别的要求也应当坚持”的运营级约束,应该用 {"role": "system"} 。而 mid-conversation system message 让你既保住这份优先级,又不用付缓存失效的代价。
3. 常见场景
- 中途改策略/人设:长会话跑了几十轮后要加新约束。
- 应用观测到的状态变化:磁盘文件变了、用户打开了 yolo 模式、使用限额跌破阈值——这些是「运营级事实」。
- 每轮都要权威注入的上下文:时效性提示、会话截止时间、可用工具变化等,变动太频繁不适合放固定前缀。
- 不打断 agentic 循环的用户插话:Claude 正在跑工具时用户又发了一句话,把它作为系统消息放在下一个工具结果之后,Claude 就能把新输入融入当前工作,而不是当成一个全新请求去切换。(这个展开讲)
3.1 放置规则 与 用户插话
首先规则如下:
- 不能是 messages 的第一条(整个对话的起始指令需要用顶层 system prompt)。
- 必须紧跟在一个 user 回合之后(带 tool_result 的 user 消息也算),或紧跟服务端工具结果结束的 assistant 回合之后。
- 且它必须是数组最后一条,或者后面紧跟一个 assistant 回合。
- 绝不能插在 tool_use 和对应的 tool_result 之间。
最后一条规则影响较大,直接导致了这个功能可以被用于用户插话机制。
由于 system message 不能夹在 tool_use 和它的 tool_result 之间,而工具刚跑完、结果刚回来的那一刻,正是 Claude 准备决定「下一步干嘛」的时机。把新输入插在这里,Claude 在规划下一步时就能顺带把新信息一起考虑进去,衔接最自然,也不违反放置规则。
4. 和 claude code system reminder 的对比
简单记: <system-reminder> 是「伪装成文本的提醒」(应用层惯例),mid-conversation system message 是「协议承认的正牌系统消息」(API 特性)。前者是后者出现之前的替代方案,现在有了正规渠道。
| mid-conversation system message | <system-reminder> |
|
|---|---|---|
| 是什么 | API 协议里的一种消息角色 | 注入到消息文本内容里的一段标记 |
| 位置 | messages 数组里独立的一条 {"role": "system"} | 塞在某条 user/tool_result 消息的 content 内部 |
| 谁定义的 | Anthropic 的 Messages API 正式特性 | 应用层/harness(比如 Claude Code)自己约定的一个 XML 标签 |
| 模型怎么看 | 是一条真正的、带系统级优先权的独立消息 | 本质还是普通文本。只是用标签提示「这段是背景注入」 │ |
| 优先级 | 系统级,冲突时压过 user | 没有协议层的特殊权重,靠训练/约定来「当回事」 |
