⚠️ 学习声明:本文档基于 Claude Code 2.1.88 源码分析整理,仅供个人学习研究使用,不做任何商业用途。
这是决定模型行为的最上游配置,所有工具调用、角色定位、安全约束都在此定义。
一、System Prompt 的整体架构
Claude Code 的 System Prompt 不是一段静态字符串,而是多段动态内容的拼接,在每次会话启动时按优先级构建:
buildEffectiveSystemPrompt() 优先级(高 → 低): |
二、默认 System Prompt 的组成(getSystemPrompt())
prompts.ts 中的 getSystemPrompt() 拼接了以下逻辑段落,每段都是一个命名的 Section:
2.1 核心行为规范(静态,可全局缓存)
| Section | 内容摘要 |
|---|---|
| Intro | 角色定位:interactive agent for software engineering tasks |
| System | 工具调用规则、权限模式说明、system-reminder 标签处理 |
| Doing Tasks | 编码风格约束(不过度注释、不多余抽象、最小代码改动) |
| Proactiveness | 何时主动补充信息 vs 只做被要求的事 |
| Synthetic Messages | 如何处理 harness 注入的合成消息 |
| Images | 图像输入的处理规则 |
| Environment | OS、shell、工作目录、Git 状态注入 |
| Tool Use | 工具调用的通用规则 |
2.2 动态 Section(每次会话重新计算)
| Section | 内容 | 是否缓存 |
|---|---|---|
memory |
从 memdir 加载的自动记忆内容 |
否(每次加载) |
mcp-instructions |
MCP 服务器的工具说明 | 否(按连接状态) |
output-style |
用户配置的输出风格 | 是(session 级) |
language |
语言偏好(中文/英文等) | 是 |
todo-tools |
Todo 工具相关指令 | 是 |
scratchpad |
草稿本目录信息 | 是 |
worktree |
Git Worktree 状态 | 否(实时) |
三、Section 缓存机制(systemPromptSections.ts)
System Prompt 的计算开销不小(读文件、查 Git 状态等),CC 用命名缓存避免每次重复计算:
// 缓存版本(session 内只算一次) |
缓存清除时机:/clear 命令 或 /compact 压缩时,clearSystemPromptSections() 清空所有缓存,确保新会话获得新鲜的 prompt。
四、静态/动态边界(API 侧缓存优化)
prompts.ts 中有一个关键常量:
export const SYSTEM_PROMPT_DYNAMIC_BOUNDARY = '__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__' |
这个边界将 System Prompt 数组分为两部分:
[静态部分(跨用户/跨会话不变)] ← scope: 'global',Anthropic API 跨 org 缓存 |
工程价值:静态部分可以命中 Anthropic 服务端的 prompt cache,大幅减少 token 消耗和首 token 延迟。
五、关键编码风格约束(面试必备)
prompts.ts 中的 getSimpleDoingTasksSection() 包含了 CC 对 Claude 行为最核心的约束,这些是 Anthropic Prompt Engineering 的精华:
代码修改最小化原则: |
这些约束直接影响模型的输出行为,是 Prompt Engineering 的核心实践。
六、多角色 System Prompt 变体
CC 根据运行模式选择不同的 System Prompt:
6.1 标准交互模式
getSystemPrompt() → 完整的编程助手 prompt,约 8000+ tokens
6.2 Coordinator 模式
// 当 CLAUDE_CODE_COORDINATOR_MODE=true |
6.3 SubAgent 模式(AgentTool)
// AgentDefinition.getSystemPrompt() |
6.4 Proactive 模式(KAIROS)
// proactive 模式:agent prompt APPEND 到 default,而非替换 |
6.5 Override 模式(loop-mode/SDK)
// 完全替换所有 prompt |
七、Environment 注入(动态上下文)
System Prompt 中注入了运行时上下文,让 Claude 了解当前工作环境:
// 注入内容示例(DANGEROUS_uncached,每次请求刷新) |
注意这是 **DANGEROUS_uncachedSystemPromptSection**——因为工作目录可能在会话中变化(如 cd 命令)。
八、MCP 工具的动态注入
当用户配置了 MCP 服务器后,System Prompt 中会追加 MCP 工具的说明:
function getMcpInstructionsSection(mcpClients): string | null { |
这让 Claude 能”知道”并正确调用外部 MCP 工具。
九、Hooks 感知
System Prompt 中包含对 Hooks 的说明,让模型理解 hook 反馈的来源:
function getHooksSection(): string { |
十、面试要点
Q:CC 的 System Prompt 是静态的还是动态的?
混合式。核心行为规范是静态的(可 API 侧缓存),环境信息、记忆内容、MCP 工具说明是动态的(每次重新注入)。通过
SYSTEM_PROMPT_DYNAMIC_BOUNDARY分界,静态部分命中全局 prompt cache。
Q:为什么要限制模型”只做被要求的事”?
实际工程观察:模型经常”过度帮助”——修 bug 时顺手重构周边代码、加不必要的注释、创建额外的抽象层。这些”好意”改动在 code review 中制造噪音,破坏 git blame,引入未经测试的改动。
getSimpleDoingTasksSection()中的约束是 Anthropic 经过大量实践总结的 Prompt Engineering 精华。
Q:不同 Agent 角色如何共享部分 prompt?
通过分层替换:默认 prompt 是基础,
agentSystemPrompt在大多数情况下替换它(避免冲突),但在 proactive 模式下是追加(保留基础能力 + 叠加专有指令)。Override 模式完全接管,用于 SDK 自定义场景。
深度补充:System Prompt 源码精读
以下内容基于对
prompts.ts(915行)、systemPromptSections.ts、systemPrompt.ts、systemPromptType.ts及api.ts的逐行分析,面向 DeepSeek 等顶级 AI 公司面试场景,提供可以直接引用源码的深度答案。
十一、Section 缓存机制:两层缓存的完整架构
Claude Code 的 System Prompt 缓存分为两个独立层次,经常被混淆,面试必须分清。
第一层:进程内 Section 缓存(In-process Memoization)
定义在 src/constants/systemPromptSections.ts,核心数据结构极为简洁:
// systemPromptSections.ts |
两个工厂函数控制缓存策略:
// 可缓存:计算一次,直到 /clear 或 /compact 才失效 |
注意 DANGEROUS_ 前缀是刻意的命名规范——强迫开发者正视”每轮重算会破坏 Anthropic API 侧缓存”这一代价。
resolveSystemPromptSections 核心逻辑:
export async function resolveSystemPromptSections( |
缓存存储位置:src/bootstrap/state.ts 中的 STATE 单例对象,字段 systemPromptSectionCache: Map<string, string | null>。初始化为空 Map,生命周期跟随进程。
缓存失效时机:clearSystemPromptSections() 在 /clear 和 /compact 命令执行时调用,同时还清除 beta header latches:
export function clearSystemPromptSections(): void { |
哪些 Section 可全局缓存,哪些必须每次重算
getSystemPrompt() 中完整的 Section 注册列表(prompts.ts L491-L555):
| Section 名称 | 工厂函数 | 原因 |
|---|---|---|
session_guidance |
systemPromptSection(可缓存) |
工具集在会话内固定 |
memory |
systemPromptSection(可缓存) |
记忆文件读取开销大,会话内稳定 |
ant_model_override |
systemPromptSection(可缓存) |
仅依赖环境变量,会话内不变 |
env_info_simple |
systemPromptSection(可缓存) |
cwd/platform/OS 版本会话内不变 |
language |
systemPromptSection(可缓存) |
语言偏好由 settings 决定,不变 |
output_style |
systemPromptSection(可缓存) |
输出风格配置会话内固定 |
mcp_instructions |
DANGEROUS_uncachedSystemPromptSection |
MCP 服务器可能在两轮之间连接/断开 |
scratchpad |
systemPromptSection(可缓存) |
scratchpad 路径会话内固定 |
frc |
systemPromptSection(可缓存) |
FRC 配置不变 |
summarize_tool_results |
systemPromptSection(可缓存) |
静态文本 |
token_budget |
systemPromptSection(可缓存) |
曾是 DANGEROUS,PR 优化后改为缓存 |
核心规律:只有 mcp_instructions 被标记为 DANGEROUS_uncached,原因是 MCP 服务器是在用户会话运行过程中热插拔的——用户可以在两轮对话之间新建 MCP 连接。如果缓存 MCP 指令,模型就不知道新连上的工具,产生幻觉调用。
第二层:Anthropic API 侧缓存(Prompt Cache / cache_control)
进程内缓存决定”是否重新计算文本”,API 侧缓存决定”是否向 Anthropic 服务器重新传输并编码 KV”。两者正交,必须同时满足才能节省成本。
SYSTEM_PROMPT_DYNAMIC_BOUNDARY 的作用(prompts.ts L114):
export const SYSTEM_PROMPT_DYNAMIC_BOUNDARY = '__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__' |
这是插入到 string[] 中的一个哨兵字符串(不是实际发送给模型的内容)。splitSysPromptPrefix() 在 api.ts 中检测它,将 prompt 切分为静态段和动态段:
getSystemPrompt() 返回的 string[] 结构: |
splitSysPromptPrefix() 三种路径(api.ts L321-L435):
路径1(MCP 工具存在,skipGlobalCacheForSystemPrompt=true):
- 跳过全局缓存,改用
org级缓存 - 返回:
attribution(null)+prefix(org)+rest(org)
路径2(全局缓存模式,找到边界标记):
- 返回:
attribution(null)+prefix(null)+static(global)+dynamic(null) static部分打上cache_control: { type: 'ephemeral', scope: 'global' },可跨 org 共享缓存
路径3(默认/边界缺失):
- 返回:
attribution(null)+prefix(org)+rest(org)
为什么 MCP 工具存在时不能用全局缓存?
工具 schema 是通过系统提示传递给模型的,不同用户配置了不同的 MCP 工具,工具列表不同,prompt 内容就不同,无法全局共享。
十二、getSystemPrompt() 完整调用链
从函数入口到最终 HTTP body 的完整路径,基于源码还原:
用户发消息 |
十三、prompts.ts 核心行为约束的实际文本
以下直接引用源码原文,不做改写,面试时这些是”为什么这样设计”的一手材料。
过度帮助的克制(L201-L203)
"Don't add features, refactor code, or make 'improvements' beyond what was asked. |
不要给时间估计(L232)
"Avoid giving time estimates or predictions for how long tasks will take, whether |
失败后的诊断规范(L233)
"If an approach fails, diagnose why before switching tactics—read the error, check |
安全意识(L234)
"Be careful not to introduce security vulnerabilities such as command injection, XSS, |
兼容性删除的约束(L236)
"Avoid backwards-compatibility hacks like renaming unused _vars, re-exporting types, |
Ant 内部版独有:诚实报告规范(L240,仅 USER_TYPE=ant 时注入)
"Report outcomes faithfully: if tests fail, say so with the relevant output; if you |
设计模式:Ant 内部版(USER_TYPE=ant)和外部版(USER_TYPE 未设置)注入的约束文本不同。这是一种受众差异化 Prompt——内部工程师需要更严格的诚实规范,外部用户需要更友好的引导。
十四、Environment Section 注入细节
computeSimpleEnvInfo()(prompts.ts L651-L710)构建的完整 Environment Section:
const envItems = [ |
最终格式:
# Environment |
MCP 工具注入(getMcpInstructions(),L579-L603):
// 过滤出已连接且提供了 instructions 的 MCP 服务器 |
注意:仅有 instructions 字段的 MCP server 才会写入 system prompt。纯粹只暴露工具(无 instructions)的 MCP server 不在此处出现,其工具 schema 通过 API 的 tools 参数单独传递。
SubAgent 的 envInfo 注入(enhanceSystemPromptWithEnvDetails(),L760-L791):
SubAgent 不走 getSystemPrompt(),而是由调用方调用 enhanceSystemPromptWithEnvDetails(),追加:
- Notes(绝对路径要求、no-emoji 等)
- DiscoverSkills 指引(可选)
computeEnvInfo()(与主线程类似,但格式略不同,用<env>XML 标签包裹)
十五、三种 Agent 变体的 Prompt 差异
变体一:Default(标准 Claude Code)
完整的静态行为规范 + 动态 Section 注册表。getSystemPrompt() 返回值经过 buildEffectiveSystemPrompt() 后作为 defaultSystemPrompt 使用。
[静态层] |
变体二:Coordinator(多 Agent 协调模式)
触发条件:COORDINATOR_MODE feature flag 开启 && CLAUDE_CODE_COORDINATOR_MODE 环境变量为真 && 无 mainThreadAgentDefinition。
// systemPrompt.ts L63-L74 |
Coordinator prompt 完全替换默认 prompt,专注于任务分解和 SubAgent 调度,不包含面向用户交互的行为规范。
变体三:Proactive(自主 Agent / KAIROS 模式)
触发条件:PROACTIVE 或 KAIROS feature flag 开启 && isProactiveActive() 返回 true。
// prompts.ts L471-L488(proactive 路径) |
关键差异:
- 省略全部静态行为规范(无 DoingTasksSection、无 ActionsSection 等)
- 不使用 Section 注册表(
dynamicSections数组跳过),直接并发计算 getProactiveSection()注入独有的自主工作规范:Tick 处理、Pacing、Terminal Focus 感知
Proactive 模式的 Agent 指令合并(当有 mainThreadAgentDefinition):
// systemPrompt.ts L107-L113 |
Standard 模式下 agentSystemPrompt 替换 defaultSystemPrompt,Proactive 模式下追加到末尾。
十六、Hooks 感知的完整注入路径
Hooks 信息在 prompt 中的注入涉及两处:
1. 主 System Prompt 中的 Hooks Section
// prompts.ts L127-L129 |
该函数被 getSimpleSystemSection() 调用,作为 # System 节下的一个 bullet point 注入。因此它属于静态内容(在 SYSTEM_PROMPT_DYNAMIC_BOUNDARY 之前),享受 global cache。
2. getSystemRemindersSection() 中的 system-reminder 标签说明
// prompts.ts L131-L134 |
Hooks 执行后的反馈通过 <user-prompt-submit-hook> 标签传入对话,模型被告知这类标签的来源是用户,而不是工具调用结果的副产品。
设计意图:Hooks 是用户在 settings.json 中配置的 shell 脚本,在工具调用前/后拦截执行。如果模型不知道 hooks 的存在,当 hook 阻断了某个 bash 命令,模型会困惑”工具明明应该成功却失败了”。通过在 system prompt 中明确告知 hooks 机制,模型能正确处理 hook 反馈:理解阻断原因 → 调整行为 → 或引导用户修改 hook 配置。
十七、面试深度题(基于源码的五道硬核问答)
Q1:为什么静态内容必须放在 System Prompt 开头,而不是末尾?
答:这是 Anthropic API 的 prompt caching 工作原理决定的。API 侧缓存是前缀匹配:只有当某个 token 位置之前的所有内容与上一次请求完全一致时,才能命中缓存。如果把频繁变动的动态内容(如记忆、MCP 工具)放在开头,静态内容放在末尾,那么每次动态内容一变,静态内容的缓存就全部失效——尽管静态文本本身没有任何变化。
将静态内容放在开头(大约 3000-5000 token 的行为规范),通过 SYSTEM_PROMPT_DYNAMIC_BOUNDARY 切分,使 Anthropic API 能对静态前缀打上 scope: 'global' 的缓存标记,跨用户、跨组织共享同一批 KV cache。动态内容(会话记忆、环境信息、MCP 指令)紧随其后,每次可能变化,但变化不影响前面静态部分的缓存命中。
量化影响:静态前缀约占 prompt 总长度的 50-70%,全局缓存命中率提升后,每次 API 调用的 input token 处理成本可降低 40-60%(Anthropic 的 prompt cache 定价约为标准价的 10%)。
Q2:mcp_instructions 为什么是唯一的 DANGEROUS_uncached Section?进程内缓存和 API 侧缓存在此如何联动?
答:MCP 服务器可以在两次对话轮次之间热插拔。用户在终端开一个新的 MCP 服务器后发消息,Claude Code 在 getSystemPrompt() 执行时检测到新连接。如果 mcp_instructions 使用了进程内缓存,返回的还是上一轮(不含新服务器)的指令,模型就不知道新工具的存在,可能产生幻觉调用。
联动机制:DANGEROUS_uncachedSystemPromptSection 设 cacheBreak: true,resolveSystemPromptSections() 每轮都跳过进程内缓存直接执行 getMcpInstructionsSection(mcpClients)。若 MCP 工具列表发生变化,整个 MCP Section 文本变化,导致 API 侧的 prompt 后缀变化。由于静态前缀不变,global cache 仍然命中;只有动态尾部(cacheScope: null)需要重新编码——这是故意设计的权衡:保障 MCP 实时性,同时静态前缀的缓存红利不受影响。
此外,isMcpInstructionsDeltaEnabled() 功能开关(L511-L519)提供了另一个优化路径:通过”增量更新附件”机制(mcp_instructions_delta)代替每轮全量写入 system prompt,彻底避免 DANGEROUS_uncached 打破 API 侧缓存。
Q3:SystemPrompt 是 readonly string[] 加品牌类型,为什么不直接用 string?
答:src/utils/systemPromptType.ts 的品牌类型设计有三个工程目标:
类型安全:防止普通
string[]被意外传入需要SystemPrompt的函数(如splitSysPromptPrefix、buildSystemPromptBlocks)。错误在编译期暴露,而不是运行时产生格式不符的 API 请求。多块结构保留:保持
string[]而非拼接成单个string,是为了让splitSysPromptPrefix()能通过SYSTEM_PROMPT_DYNAMIC_BOUNDARY哨兵字符串定位边界。如果提前拼接,边界信息丢失,无法切分静态/动态块。循环依赖隔离:
systemPromptType.ts被注释为”intentionally dependency-free”,任何模块都能 import 它而不引发初始化循环。而systemPrompt.ts中有proactiveModule、coordinatorModule等延迟 require,如果 type 定义放在那里,会触发循环加载。
Q4:Proactive 模式为什么不走 Section 注册表(dynamicSections),而是直接 await 所有内容?
答:getSystemPrompt() 在检测到 proactive 激活后(L467-L489)提前返回,完全跳过了 dynamicSections 数组的构建和 resolveSystemPromptSections() 调用。
原因在于缓存语义差异:
Proactive prompt 的静态内容极少:整个 proactive prompt 几乎全是动态内容(记忆、当前环境、Tick 状态),不存在需要缓存的大静态前缀,Section 注册表的缓存优化价值接近零。
生命周期不同:普通会话的 sections 在
/clear之前稳定,Proactive 会话中每次 Tick 唤醒都可能触发记忆更新或 MCP 状态变化,进程内缓存命中率极低,反而增加 stale 风险。简化路径:Proactive 走独立的快速路径,减少 Section 注册表的维护复杂度,且
filter(s => s !== null)直接处理可选 sections,无需封装。
Q5:USER_TYPE=ant 的条件判断在构建时如何处理?外部发布版和内部版的 prompt 大小差异有多大?
答:process.env.USER_TYPE 是通过 Bun 构建时 define(类似 webpack 的 DefinePlugin)注入的编译期常量。USER_TYPE === 'ant' 在外部构建中被替换为 false,死代码消除(DCE)工具随后删除所有 if (false) 分支,外部用户的产物中完全不存在内部版 prompt 文本。
内部版(ant)额外注入的约束:
getSimpleDoingTasksSection()中约 5 条额外 bullet(注释写作规范、诚实报告规范、False-claims 缓解规范)getOutputEfficiencySection()返回更长的”Communicating with the user”段落(约 400 词 vs 外部版约 100 词)getSimpleDoingTasksSection()中的协作者身份描述(”You’re a collaborator, not just an executor”)/issue和/share斜杠命令说明 + Slack 反馈渠道指引
估算差异:内部版 system prompt 比外部版多约 800-1200 tokens(基于各段字数估算)。由于内部工程师的 API 调用成本由 Anthropic 内部承担,这些额外指令的代价是可接受的,换来的是更严格的行为规范和更高质量的内部工具体验。
源码注释中的 // @[MODEL LAUNCH] 标记(如 L202 的 capy v8 thoroughness counterweight)揭示了这些 Ant 专属指令的真实动机:新模型发布时,针对该模型的特定行为偏差(过度注释、虚假声明等)定向注入矫正指令,在 A/B 测试验证后再推广到外部版本。
十八、补充:buildEffectiveSystemPrompt 的优先级决策树
overrideSystemPrompt 存在? |
appendSystemPrompt 在除 override 外的所有路径都会追加——这是设计给”追加式定制”用的接口,用户可以通过 --append-system-prompt 在不替换默认 prompt 的前提下注入额外指令(如公司代码规范、项目约定)。
涉及源文件
src/constants/prompts.tssrc/constants/systemPromptSections.tssrc/utils/systemPrompt.ts


