claude-code-learn_
03SYSTEM PROMPT

03 - 系统提示词

系统提示词是 Agent 的"宪法"。它在每次 API 调用中随 system[] 字段送达模型,决定模型的身份、行为边界、可感知的环境状态。

读完你能回答

问题
系统提示词由哪些区块组成?为什么不是一段连续文本?
"静态 / 动态"的区分和 prompt cache 是什么关系?
CLAUDE.md 在哪里进到系统提示词?按什么顺序合并?
<system-reminder>system[] 有什么区别?

结构:一段"分层三明治"

系统提示词在 HTTP 请求体里是一个 system[] 数组。数组里每段文本是一块,各自职责不同,而且带不同的 cache_control 标记。把稳定的放前面易变的放后面,是为了让 Anthropic API 的前缀缓存能最大限度命中。

缓存边界(虚线)之前的部分一旦固定就几乎不变,第二次及以后的请求能直接复用;之后的部分每次会话可能不同,需要重新处理。

区块 属性 典型内容
身份声明 静态 "You are Claude Code..." 自我介绍
核心行为准则 静态 工具使用规范、安全要求、输出风格
任务执行规范 静态 拆解任务、维护待办、提交代码
环境信息 动态 cwd、平台、Shell、OS 版本、Git 状态
会话元信息 动态 模型标识、知识截止日期、当前日期
项目规则 动态 逐级合并的 CLAUDE.md
Skills 清单 动态 当前可用 Skills 与触发条件
展开一次真实 system[] 数组的骨架
{
  "system": [
    {
      "type": "text",
      "text": "You are Claude Code, Anthropic's official CLI for Claude. ...",
      "cache_control": { "type": "ephemeral" }
    },
    {
      "type": "text",
      "text": "# 核心行为准则\n- Prefer editing existing files to creating new ones.\n- Do not add comments unless the WHY is non-obvious.\n- ...",
      "cache_control": { "type": "ephemeral" }
    },
    {
      "type": "text",
      "text": "# 环境\nPrimary working directory: /Volumes/kingston/code/claude-code-learn\nPlatform: darwin\nShell: zsh\nOS Version: Darwin 25.3.0\nToday's date is 2026-04-20."
    },
    {
      "type": "text",
      "text": "# 项目规则\n<来自 ~/.claude/CLAUDE.md 与 <repo>/CLAUDE.md 合并后的内容>"
    },
    {
      "type": "text",
      "text": "# 可用 Skills\n- simplify: ...\n- less-permission-prompts: ...\n- ..."
    }
  ]
}

值得看一眼的细节:

  • 静态三段打上 cache_control: ephemeral,动态部分不打
  • ephemeral 缓存 TTL 约 5 分钟,停顿过久需要重建前缀缓存
  • 没有 cache_control 的段不影响缓存,但会被"推到"缓存边界之后

缓存:为什么顺序这么关键

Anthropic API 的 prompt cache 是纯前缀匹配。哪怕只有一个字节不一样,缓存就从那个字节开始失效。

前缀状态 后续内容 命中?
第 1 到 N byte 完全相等 任意 ✓ 命中
第 K byte 不同(K ≤ N) 之前的部分相同 只命中前 K-1 byte,之后全重算
距上次命中超过 TTL 任意 × 未命中,重建

这就是为什么不能把动态信息(当前日期、cwd)放在静态规则之前——只要这些内容稍一变化,后面所有静态规则的缓存都作废。

展开一个"放错位置"的反例
❌ 错误排法:
[动态] 当前日期: 2026-04-20
[动态] 工作目录: /repo
[静态] 身份: You are Claude Code...
[静态] 行为准则: ...

结果:日期一变(每天一次),整个 system[] 前缀全部失效,所有静态规则重新计费。
✓ 正确排法(Claude Code 采用的):
[静态] 身份: You are Claude Code...
[静态] 行为准则: ...
[静态] 执行规范: ...
        ←← cache boundary
[动态] 当前日期
[动态] 工作目录
[动态] 项目规则
[动态] Skills 清单

结果:日期/目录/规则变动只影响动态段,静态前缀持续复用。

动态区块来源

区块 数据来源
环境信息 进程环境变量、unamepwdgit status
会话元信息 运行时配置 + 系统时钟
项目规则 ~/.claude/CLAUDE.md<repo>/CLAUDE.md → 子目录 CLAUDE.md 合并
Skills 清单 扫描 ~/.claude/skills/ 与项目 .claude/skills/

CLAUDE.md 的合并顺序

合并从外到内,更具体的覆盖更泛的。冲突时以最内层为准。

展开一个合并示例

假设全局偏好用中文,项目要求英文 commit:

# ~/.claude/CLAUDE.md
- 日常沟通使用简体中文
- Commit message 使用英文
# <repo>/CLAUDE.md
- 分支命名必须以 feat/ 或 fix/ 开头
- 测试必须连真实数据库

注入 system[] 的"项目规则"段是两者拼接,后者在下:

[来自 ~/.claude/CLAUDE.md]
- 日常沟通使用简体中文
- Commit message 使用英文

[来自 <repo>/CLAUDE.md]
- 分支命名必须以 feat/ 或 fix/ 开头
- 测试必须连真实数据库

如果两份都写了"commit message 用 X",后者(项目级)生效。

system[] vs <system-reminder>

系统提示词只在 API 的 system[] 字段里。运行过程中若需要"打断"式提示(例如"Skills 列表变了""工具调用有更新"),会通过 <system-reminder> 标签注入到 user 消息里,而不是修改 system[]

载体 可缓存性 生命周期 用途
system[] 可缓存前缀 整个会话 身份、规则、环境
<system-reminder> 随 user 消息传输,不命中前缀缓存 单次 turn 临时提醒、状态变更
展开一次 system-reminder 的真实形态

它不是独立消息,而是塞进 user content 里的一段 XML 标签:

{
  "role": "user",
  "content": [
    { "type": "text", "text": "<system-reminder>\nSkills 列表有更新:新增 /deploy-staging\n</system-reminder>" },
    { "type": "text", "text": "帮我部署一下" }
  ]
}

模型被训练识别 <system-reminder> 标签并把它当作系统侧的指示而非用户的话。但因为它在 user 消息里,每次 turn 都会重新传输,不享受前缀缓存折扣。

这正是它的设计意图:用它做短暂提醒,不用它做长期规则

设计意图

把规则写死在系统提示词里,而不是每次用户消息前拼接,是出于两个考虑:

动机 解释
缓存友好 静态前缀稳定 = 缓存命中率高,延迟和成本都下来
权限清晰 用户消息可被视作"不可信输入",系统提示词是"可信规则";把敏感约束放在 system[] 降低 prompt injection 风险