11---
22title : " System Prompt 动态组装 - AI 工作记忆构建"
3- description : " 深入解析 Claude Code 的 System Prompt 动态组装过程:如何将 CLAUDE.md、项目上下文、工具定义和用户偏好拼装为 AI 的工作记忆 。"
4- keywords : ["System Prompt", "系统提示词", "动态组装", "CLAUDE.md", "上下文构建 "]
3+ description : " 深入解析 Claude Code 的 System Prompt 动态组装过程:缓存策略、分界标记、Section 注册表、 CLAUDE.md 多级合并,以及如何将零散上下文拼装为 API 可消费的缓存友好结构 。"
4+ keywords : ["System Prompt", "系统提示词", "动态组装", "CLAUDE.md", "Prompt Cache", "缓存策略 "]
55---
66
7- { /* 本章目标:解释 System Prompt 的组装过程和设计思想 */ }
7+ ## 从数组到 API 调用: System Prompt 的完整链路
88
9- ## 什么是 System Prompt
9+ System Prompt 在 Claude Code 中不是一段写死的文本,而是一个 ** ` string[] ` 数组 ** (品牌类型 ` SystemPrompt ` ,定义于 ` src/utils/systemPromptType.ts:8 ` ),经过组装、分块、缓存标记后发送给 API。
1010
11- 每次调用 AI API 时,都需要发送一个 System Prompt——它是 AI 的"人设说明书",告诉 AI:
11+ ### 三阶段管道
1212
13- - 你是谁(Claude Code,一个编程助手)
14- - 你能做什么(可用工具列表)
15- - 你在什么环境(操作系统、当前目录、git 状态)
16- - 你需要遵守什么规则(安全规范、输出格式)
13+ ```
14+ getSystemPrompt() → string[] (组装内容)
15+ ↓
16+ buildEffectiveSystemPrompt() → SystemPrompt (选择优先级路径)
17+ ↓
18+ buildSystemPromptBlocks() → TextBlockParam[] (分块 + cache_control 标记)
19+ ```
20+
21+ 1 . ** ` getSystemPrompt() ` ** (` src/constants/prompts.ts:444 ` )—— 收集静态段 + 动态段,插入 ` SYSTEM_PROMPT_DYNAMIC_BOUNDARY ` 分界标记
22+ 2 . ** ` buildEffectiveSystemPrompt() ` ** (` src/utils/systemPrompt.ts:41 ` )—— 按 Override > Coordinator > Agent > Custom > Default 优先级选择
23+ 3 . ** ` buildSystemPromptBlocks() ` ** (` src/services/api/claude.ts:3214 ` )—— 调用 ` splitSysPromptPrefix() ` 分块,为每个块附加 ` cache_control `
24+
25+ ## SystemPrompt 品牌类型
26+
27+ ``` typescript
28+ // src/utils/systemPromptType.ts:8
29+ export type SystemPrompt = readonly string [] & {
30+ readonly __brand: ' SystemPrompt'
31+ }
32+ export function asSystemPrompt(value : readonly string []): SystemPrompt {
33+ return value as SystemPrompt // 零开销类型断言
34+ }
35+ ```
36+
37+ 品牌类型(branded type)防止普通 ` string[] ` 被意外传入 API 调用——只有通过 ` asSystemPrompt() ` 显式转换才能获得 ` SystemPrompt ` 类型。
38+
39+ ## getSystemPrompt():内容组装的全景
40+
41+ ` src/constants/prompts.ts:444 ` 是 System Prompt 的核心工厂函数,返回一个有序数组:
42+
43+ | 阶段 | 内容 | 缓存策略 |
44+ | ------| ------| ----------|
45+ | ** 静态区** | Intro Section、System Rules、Doing Tasks、Actions、Using Tools、Tone & Style、Output Efficiency | 可跨组织缓存(` scope: 'global' ` ) |
46+ | ** BOUNDARY** | ` SYSTEM_PROMPT_DYNAMIC_BOUNDARY = '__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__' ` | 分界标记(不发送给 API) |
47+ | ** 动态区** | Session Guidance、Memory、Model Override、Env Info、Language、Output Style、MCP Instructions、Scratchpad、FRC、Summarize Tool Results、Token Budget、Brief | 每次会话不同(` scope: 'org' ` 或无缓存) |
48+
49+ ### 动态区的 Section 注册表
50+
51+ 动态区通过 ` systemPromptSection() ` / ` DANGEROUS_uncachedSystemPromptSection() ` 注册,这两个工厂函数定义于 ` src/constants/systemPromptSections.ts ` :
52+
53+ ``` typescript
54+ // 缓存式 Section:计算一次,/clear 或 /compact 后才重新计算
55+ systemPromptSection (' memory' , () => loadMemoryPrompt ())
56+
57+ // 危险:每轮重新计算,会破坏 Prompt Cache
58+ DANGEROUS_uncachedSystemPromptSection (
59+ ' mcp_instructions' ,
60+ () => isMcpInstructionsDeltaEnabled () ? null : getMcpInstructionsSection (mcpClients ),
61+ ' MCP servers connect/disconnect between turns' // 必须给出破坏缓存的理由
62+ )
63+ ```
64+
65+ ` resolveSystemPromptSections() ` 在每轮查询时解析所有 Section,对于 ` cacheBreak: false ` 的 Section,优先使用 ` getSystemPromptSectionCache() ` 中的缓存值。只有 MCP 指令等真正动态的内容使用 ` DANGEROUS_uncachedSystemPromptSection ` 。
66+
67+ ### ` CLAUDE_CODE_SIMPLE ` 快速路径
68+
69+ 当环境变量 ` CLAUDE_CODE_SIMPLE ` 为真时,整个 System Prompt 缩减为一行:
70+
71+ ``` typescript
72+ ` You are Claude Code, Anthropic's official CLI for Claude.\n\n CWD: ${getCwd ()}\n Date: ${getSessionStartDate ()} `
73+ ```
74+
75+ 跳过所有 Section 注册、缓存分块、动态组装——用于最小化 token 消耗的测试场景。
76+
77+ ## buildEffectiveSystemPrompt():五级优先级
78+
79+ ` src/utils/systemPrompt.ts:41 ` 决定最终使用哪个 System Prompt:
80+
81+ | 优先级 | 条件 | 行为 |
82+ | --------| ------| ------|
83+ | ** 0. Override** | ` overrideSystemPrompt ` 非空 | 完全替换,返回 ` [override] ` |
84+ | ** 1. Coordinator** | ` COORDINATOR_MODE ` feature + 环境变量 | 使用协调者专用提示词 |
85+ | ** 2. Agent** | ` mainThreadAgentDefinition ` 存在 | Proactive 模式:追加到默认提示词尾部;否则:替换默认提示词 |
86+ | ** 3. Custom** | ` --system-prompt ` 参数指定 | 替换默认提示词 |
87+ | ** 4. Default** | 无特殊条件 | 使用 ` getSystemPrompt() ` 完整输出 |
88+
89+ ` appendSystemPrompt ` 始终追加到末尾(Override 除外)。
90+
91+ ## 缓存策略:分块、标记、命中
92+
93+ 这是 System Prompt 设计中最精密的部分。
94+
95+ ### Anthropic Prompt Cache 基础
96+
97+ Anthropic API 的 Prompt Cache 允许跨请求复用相同的 System Prompt 前缀,按缓存命中量计费(远低于完整输入价格)。缓存键由内容的 Blake2b 哈希决定——任何字符变化都会导致缓存失效。
98+
99+ ### ` splitSysPromptPrefix() ` :三种分块模式
100+
101+ ` src/utils/api.ts:321 ` 是缓存策略的核心,根据条件选择三种分块模式:
102+
103+ #### 模式 1:MCP 工具存在时(` skipGlobalCacheForSystemPrompt=true ` )
104+
105+ ```
106+ [attribution header] → cacheScope: null (不缓存)
107+ [system prompt prefix] → cacheScope: 'org' (组织级缓存)
108+ [everything else] → cacheScope: 'org' (组织级缓存)
109+ ```
110+
111+ MCP 工具列表在会话中可能变化(连接/断开),破坏了跨组织缓存的基础,因此降级为组织级。
112+
113+ #### 模式 2:Global Cache + Boundary 存在(1P 专用)
17114
18- ## 不是静态模板,而是动态组装
115+ ```
116+ [attribution header] → cacheScope: null (不缓存)
117+ [system prompt prefix] → cacheScope: null (不缓存)
118+ [static content] → cacheScope: 'global' (全局缓存!跨组织共享)
119+ [dynamic content] → cacheScope: null (不缓存)
120+ ```
121+
122+ 这是缓存效率最高的模式。` SYSTEM_PROMPT_DYNAMIC_BOUNDARY ` 之前的静态内容(Intro、Rules、Tone & Style 等)对所有用户相同,可跨组织缓存。
123+
124+ #### 模式 3:默认(3P 提供商 或 Boundary 缺失)
125+
126+ ```
127+ [attribution header] → cacheScope: null (不缓存)
128+ [system prompt prefix] → cacheScope: 'org' (组织级缓存)
129+ [everything else] → cacheScope: 'org' (组织级缓存)
130+ ```
131+
132+ ### ` getCacheControl() ` :TTL 决策
19133
20- Claude Code 的 System Prompt 不是一段写死的文本,而是根据当前环境** 实时组装** 的:
134+ ` src/services/api/claude.ts:359 ` 生成的 ` cache_control ` 对象:
135+
136+ ``` typescript
137+ {
138+ type : ' ephemeral' ,
139+ ttl ?: ' 1h' , // 仅特定 querySource 符合条件时
140+ scope ?: ' global' , // 仅静态区
141+ }
142+ ```
143+
144+ 1 小时 TTL 的判定逻辑(` should1hCacheTTL() ` ,第 394 行):
145+ - ** Bedrock 用户** :通过环境变量 ` ENABLE_PROMPT_CACHING_1H_BEDROCK ` 启用
146+ - ** 1P 用户** :通过 GrowthBook 配置的 ` allowlist ` 数组匹配 ` querySource ` ,支持前缀通配符(如 ` "repl_main_thread*" ` )
147+ - ** 会话级锁定** :资格判定结果在 bootstrap state 中缓存,防止 GrowthBook 配置中途变化导致同一会话内 TTL 不一致
148+
149+ ### 缓存破坏:Session-Specific Guidance 的放置
150+
151+ ` getSessionSpecificGuidanceSection() ` (` src/constants/prompts.ts:352 ` )的内容必须放在 ` SYSTEM_PROMPT_DYNAMIC_BOUNDARY ` ** 之后** 。因为它包含:
152+ - 当前会话的 enabledTools 集合
153+ - ` isForkSubagentEnabled() ` 的运行时判定
154+ - ` getIsNonInteractiveSession() ` 的结果
155+
156+ 这些运行时 bit 如果放在静态区,会产生 2^N 种 Blake2b 哈希变体(N = 运行时条件数),完全破坏缓存命中率。源码注释明确警告:
157+
158+ > Each conditional here is a runtime bit that would otherwise multiply the Blake2b prefix hash variants (2^N). See PR #24490 , #24171 for the same bug class.
159+
160+ ### ` CLAUDE_CODE_SIMPLE ` 模式
161+
162+ 当设置了 ` CLAUDE_CODE_SIMPLE ` 环境变量时,整个系统提示词会大幅缩减:
163+
164+ ``` typescript
165+ return [` You are Claude Code, Anthropic's official CLI for Claude.\n\n CWD: ${getCwd ()}\n Date: ${getSessionStartDate ()} ` ]
166+ ```
21167
22- <Frame caption = " System Prompt 的 6 大组成部分" >
23- <img src = " /docs/images/system-prompt-assembly.png" alt = " System Prompt 动态组装图" />
24- </Frame >
168+ ## 上下文注入:System Context 与 User Context
25169
26- | 组成部分 | 内容 | 来源 |
27- | ----------| ------| ------|
28- | 基础人设 | 角色定义、行为准则 | 内置模板 |
29- | 环境信息 | 操作系统、shell 类型、当前日期 | 运行时检测 |
30- | Git 状态 | 当前分支、最近提交、工作区状态 | ` git ` 命令输出 |
31- | 项目知识 | CLAUDE.md 文件内容 | 项目目录层级扫描 |
32- | 记忆文件 | 用户偏好、项目约定 | 持久化记忆系统 |
33- | 工具说明 | 每个可用工具的描述和参数 | 工具注册表 |
170+ System Prompt 数组本身不包含运行时上下文(git 状态、CLAUDE.md 内容)。上下文通过两个独立的管道注入:
171+
172+ ### System Context(` src/context.ts:116 ` )
173+
174+ ``` typescript
175+ export const getSystemContext = memoize (async () => {
176+ return {
177+ gitStatus , // git 分支、状态、最近提交(截断至 MAX_STATUS_CHARS=2000)
178+ cacheBreaker , // 仅 ant 用户的缓存破坏器
179+ }
180+ })
181+ ```
182+
183+ - 使用 ` lodash.memoize ` 缓存——** 整个会话期间只计算一次**
184+ - Git 状态快照包含 5 个并行 ` git ` 命令(branch、defaultBranch、status、log、userName)
185+ - ` status ` 超过 2000 字符时截断并附加提示使用 BashTool 获取更多信息
186+ - ` systemPromptInjection ` 变更时,通过 ` getUserContext.cache.clear?.() ` 清除所有上下文缓存
187+
188+ ### User Context(` src/context.ts:155 ` )
189+
190+ ``` typescript
191+ export const getUserContext = memoize (async () => {
192+ return {
193+ claudeMd , // 合并后的 CLAUDE.md 内容
194+ currentDate , // "Today's date is YYYY-MM-DD."
195+ }
196+ })
197+ ```
198+
199+ - ** CLAUDE.md 禁用条件** :` CLAUDE_CODE_DISABLE_CLAUDE_MDS ` 环境变量,或 ` --bare ` 模式(除非通过 ` --add-dir ` 显式指定目录)
200+ - ` --bare ` 模式的语义是"跳过我没要求的东西"而非"忽略所有"
201+
202+ ### 注入位置
203+
204+ 在 ` src/query.ts:449 ` :
205+
206+ ``` typescript
207+ // System Context 追加到 System Prompt 尾部
208+ const fullSystemPrompt = asSystemPrompt (
209+ appendSystemContext (systemPrompt , systemContext ) // 简单拼接
210+ )
211+ ```
212+
213+ User Context 通过 ` prependUserContext() ` (` src/utils/api.ts:449 ` )注入为 ` <system-reminder> ` 标签包裹的首条用户消息,放在所有对话消息之前。
214+
215+ ## Attribution Header:计费与安全
216+
217+ 每个 API 请求的 System Prompt 首块是 Attribution Header(` src/constants/system.ts:30 ` ),包含:
218+ - ** ` cc_version ` ** :Claude Code 版本 + 指纹
219+ - ** ` cc_entrypoint ` ** :入口点标识(REPL / SDK / pipe 等)
220+ - ** ` cch=00000 ` ** (NATIVE_CLIENT_ATTESTATION 启用时):Bun 原生 HTTP 层在发送前将零替换为计算出的哈希值,服务器验证此 token 确认请求来自真实 Claude Code 客户端
221+
222+ Header 始终 ` cacheScope: null ` ——它因版本和指纹不同而变化,不适合缓存。
34223
35224## CLAUDE.md:项目级知识注入
36225
@@ -49,10 +238,15 @@ Claude Code 的 System Prompt 不是一段写死的文本,而是根据当前
49238 └── /project/src/CLAUDE.md ← 子目录(模块特定)
50239```
51240
52- ## 缓存策略
241+ 加载逻辑在 ` src/utils/claudemd.ts ` 中的 ` getClaudeMds() ` 和 ` getMemoryFiles() ` 实现——从 CWD 向上遍历目录树,合并所有匹配的 CLAUDE.md 文件内容。
242+
243+ ## 设计洞察:为什么是 ` string[] ` 而非单个 ` string `
244+
245+ 将 System Prompt 设计为数组而非单段文本,是为了 ** 缓存分块** :
53246
54- System Prompt 的 token 消耗不小(可能占总量的 30%+)。为了降低成本,系统使用了缓存机制:
247+ 1 . Anthropic Prompt Cache 以 ** 内容块** (TextBlock)为缓存单位
248+ 2 . 将 System Prompt 拆为多个块,可以让不变的部分(Intro、Rules)获得独立的缓存命中
249+ 3 . 如果是单个 ` string ` ,任何一个字符变化(如日期更新)都会导致整个 System Prompt 的缓存失效
250+ 4 . ` SYSTEM_PROMPT_DYNAMIC_BOUNDARY ` 标记允许 ` splitSysPromptPrefix() ` 精确地将静态区标记为 ` scope: 'global' ` ,动态区不标记或标记为 ` scope: 'org' `
55251
56- - 不变的部分(基础人设、工具说明)可以跨请求复用
57- - 变化的部分(git 状态、记忆文件)每次重新生成
58- - 缓存节点的位置经过精心设计,最大化缓存命中率
252+ 这是 Claude Code 在 token 成本优化上的核心设计——一次典型的 System Prompt 约 20K+ tokens,通过缓存分块可以节省 30-50% 的输入 token 费用。
0 commit comments