系统提示词
Claude Code 如何拼装系统提示词:静态前缀、动态尾部、CLAUDE.md 注入、缓存边界与不同模式下的覆盖逻辑。
在模型处理第一条消息之前,Claude Code 会先拼装一份往往长达数千 token 的系统提示。它不是写死在二进制里的单段文本,而是按会话实时组装出来的:有些部分是全局稳定前缀,有些部分取决于当前目录、工具集、CLAUDE.md、MCP 连接和运行模式。
constants/prompts.ts → utils/systemPrompt.ts →
constants/systemPromptSections.ts → utils/claudemd.ts →
memdir/memdir.ts
真正重要的不是“提示里写了什么”,而是“这些内容按什么优先级进入、哪些可以缓存、哪些必须每轮重算”。这节课要看清的,就是这条装配链:优先级解析、内容工厂、动态分段、CLAUDE.md 注入与缓存边界。
优先级解析器
systemPrompt.ts — 覆盖 → 协调器 → 代理 → 自定义 → 默认
内容工厂
prompts.ts — 每个会话组装的静态+动态部分
部门登记处
systemPromptSections.ts — 记忆化 vs. 易失性段缓存
克劳德.md 加载器
claudemd.ts - 多范围文件发现,@include,frontmatter
记忆系统
memdir/memdir.ts — MEMORY.md + 自动内存注入
缓存边界
SYSTEM_PROMPT_DYNAMIC_BOUNDARY — 全局范围前缀分割
buildEffectiveSystemPrompt() in utils/systemPrompt.ts 是第一道门。 它实现了严格的优先级瀑布。 一旦找到更高优先级的源,较低层将被完全跳过。
appendSystemPrompt 是唯一总是附加的东西——它被添加到每个分支中,除非 overrideSystemPrompt 是活跃的。 就是这样
--append-system-prompt 无论您处于哪种模式,都可以正常工作。
在主动/KAIROS 模式下,代理的指令是 appended 到默认提示而不是替换它。 这反映了“队友”模式:主动默认已经是精简的自主代理身份,并且域代理添加在顶部。
// utils/systemPrompt.ts — proactive mode branch
if (agentSystemPrompt && (feature('PROACTIVE') || feature('KAIROS')) && isProactiveActive()) {
return asSystemPrompt([
...defaultSystemPrompt,
`\n# Custom Agent Instructions\n${agentSystemPrompt}`,
...(appendSystemPrompt ? [appendSystemPrompt] : []),
])
}
getSystemPrompt() in constants/prompts.ts 是主要的内容工厂。 它构建了 defaultSystemPrompt 馈入优先级解析器的数组。 该函数有一个快速逃生口:if CLAUDE_CODE_SIMPLE=1 它返回一个三行存根并立即退出。
对于正常会话,它并行收集四个运行时状态,然后组装两半 - 一个 静态半 和一个 动态半 — 由控制提示缓存范围的边界标记分隔。
// constants/prompts.ts — abbreviated assembly
export async function getSystemPrompt(tools, model, additionalDirs, mcpClients) {
const [skillToolCommands, outputStyleConfig, envInfo] = await Promise.all([
getSkillToolCommands(cwd),
getOutputStyleConfig(),
computeSimpleEnvInfo(model, additionalDirs),
])
return [
// ── Static (globally cacheable) ──
getSimpleIntroSection(outputStyleConfig),
getSimpleSystemSection(),
getSimpleDoingTasksSection(), // code-style, YAGNI, security rules
getActionsSection(), // reversibility / blast-radius guidance
getUsingYourToolsSection(enabledTools),
getSimpleToneAndStyleSection(),
getOutputEfficiencySection(),
// ── Boundary marker ──
SYSTEM_PROMPT_DYNAMIC_BOUNDARY,
// ── Dynamic (session-specific, registry-managed) ──
...resolvedDynamicSections, // session_guidance, memory, env_info_simple, ...
].filter(s => s !== null)
}
静态部分——它们包含什么
| 部分功能 | 它灌输了什么 | Scope |
|---|---|---|
getSimpleIntroSection() |
身份(“软件工程任务的交互式代理”)、URL 生成防护、CYBER_RISK_INSTRUCTION | static |
getSimpleSystemSection() |
Markdown渲染、权限模式工具审批、系统提醒标签、提示注入警告、hooks引导、自动压缩通知 | static |
getSimpleDoingTasksSection() |
任务范围(“软件工程任务”)、YAGNI/无镀金规则、无推测性抽象、安全卫生、代码风格项目符号(仅限 ant 的额外功能) | static |
getActionsSection() |
可逆性和爆炸半径指导——何时暂停和确认、危险行为分类(破坏性、难以逆转、他人可见) | static |
getUsingYourToolsSection() |
优先使用专用工具而不是 Bash、REPL 模式路径、并行工具调用、任务跟踪工具 | static |
getSimpleToneAndStyleSection() |
没有表情符号、文件:行引用、GitHub 问题格式、工具调用前没有冒号 | static |
getOutputEfficiencySection() |
简洁/散文质量规则——ant build 获得丰富的散文指南,external 获得简短的“开门见山” | static |
process.env.USER_TYPE === 'ant'。 这些是通过 Bun 死代码消除从外部二进制文件编译出来的。 每一个 // @[MODEL LAUNCH] 注释标记了在每个新模型发布时需要更新的说明。
界碑之后的一切都通过 部门登记处
定义于 constants/systemPromptSections.ts。 每个部分要么被记忆(每个会话计算一次,缓存直到 /clear or /compact)或易失性(每轮重新计算)。
// systemPromptSections.ts
export function systemPromptSection(name, compute): SystemPromptSection {
return { name, compute, cacheBreak: false } // memoized
}
export function DANGEROUS_uncachedSystemPromptSection(name, compute, reason) {
return { name, compute, cacheBreak: true } // recomputes every turn
}
resolveSystemPromptSections() 迭代注册表,返回非cacheBreak部分的缓存值,并调用 compute() 对于易失性或未缓存的。 缓存条目由该部分的键控 name 字符串并存储在
bootstrap/state.ts 所以 React 渲染周期不会丢失它们。
| 栏目名称 | Content | 缓存行为 |
|---|---|---|
session_guidance |
询问用户问题指导、交互式 shell 提示、代理工具指导、技能调用语法、验证代理合约 | memoized |
memory |
CLAUDE.md 层次结构 + MEMORY.md 自动记忆(参见§5) | memoized |
ant_model_override |
从远程配置注入的内部 ant 构建后缀 | memoized |
env_info_simple |
CWD、git 状态、平台、shell、操作系统版本、型号名称、知识截止、型号系列参考 | memoized |
language |
设置中的语言首选项→“始终使用 X 进行响应” | memoized |
output_style |
从设置加载的命名输出样式(名称+提示字符串) | memoized |
mcp_instructions |
Per-server instructions 来自连接的 MCP 服务器的块 |
volatile — MCP 服务器在轮次之间连接/断开 |
scratchpad |
每会话暂存器目录路径和使用规则 | memoized |
frc |
函数结果清除通知(CACHED_MICROCOMPACT 功能) | memoized |
token_budget |
“继续努力,直到接近目标”指令 | memoized |
Claude 的 API 支持即时缓存:相同的前缀按正常输入令牌成本的一小部分进行计费。 要利用这一点,系统提示符必须分为 稳定前缀 (所有用户都相同)和 挥发性尾部 (每个会话的内容)。
// constants/prompts.ts
export const SYSTEM_PROMPT_DYNAMIC_BOUNDARY = '__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__'
// WARNING: Do not remove or reorder this marker without updating cache logic in:
// - src/utils/api.ts (splitSysPromptPrefix)
// - src/services/api/claude.ts (buildSystemPromptBlocks)
Everything before 标记可以使用 scope: 'global' 在 API 调用中——可在所有组织中缓存。 一切 after 包含特定于会话的内容(您的 CWD、git 状态、CLAUDE.md 文件、模型 ID),并且不得全局缓存。
getSessionSpecificGuidanceSection ——它生活在边界之后——正是因为这个原因。 对于同一错误类别,评论引用“PR #24490、#24171”。
The memory 部分是加载用户放置在其文件系统中的每个 CLAUDE.md 文件的位置。 utils/claudemd.ts 跨四个范围发现文件并对其进行排名,按优先级顺序加载它们(优先级较低的首先,优先级较高的最后 - 因此模型会更多地关注它最后看到的文件)。
// utils/claudemd.ts — load order comment
// 1. Managed memory (/etc/claude-code/CLAUDE.md) — Global for all users on machine
// 2. User memory (~/.claude/CLAUDE.md) — Private global for all projects
// 3. Project memory (CLAUDE.md / .claude/CLAUDE.md — Checked into codebase
// .claude/rules/*.md)
// 4. Local memory (CLAUDE.local.md) — Private project-specific
CLAUDE.md,
.claude/CLAUDE.md,以及每一个 .md 文件输入 .claude/rules/。 更接近 CWD 的文件出现在阵列中的较晚位置,从而赋予它们更高的有效优先级。
@include 指令
CLAUDE.md 文件可以引用其他文件 @path 指令放置在叶文本中(不在代码块内)。 支持的表格:
# CLAUDE.md
@shared-rules.md # relative path (same dir)
@./scripts/lint-conventions.md # explicit relative
@~/company/global-standards.md # home-relative
@/absolute/path/to/rules.md # absolute
包含的文件插入为 之前的单独条目 包含文件。 通过处理路径集可以防止循环引用。 不存在的目标会被默默地跳过。 加载程序限制包含大量文本文件扩展名的允许列表 - 拒绝二进制文件(图像、PDF)以防止意外的上下文膨胀。
Frontmatter 路径过滤
CLAUDE.md 文件可以携带 YAML frontmatter 和 paths 钥匙。 只有这些 glob 模式下的文件才会收到指令。 这使您可以将特定于语言或特定于目录的规则放置在单个 CLAUDE.md 中,而不会污染每个项目。
---
paths:
- src/components/**
- "*.tsx"
---
Always use named exports in React components.
内存指令包装器
每个加载的内存文件都包含在由注入的标头中 claudemd.ts:
const MEMORY_INSTRUCTION_PROMPT =
'Codebase and user instructions are shown below. Be sure to adhere to these ' +
'instructions. IMPORTANT: These instructions OVERRIDE any default behavior and ' +
'you MUST follow them exactly as written.'
这是您在自己的序言中看到的 <system-reminder> 语境。 各个文件内容由明确标记的块分隔,标识每个范围(用户内存、项目内存、本地内存),以便模型可以推断来源。
MEMORY.md 自动记忆
memdir/memdir.ts 处理位于顶部的单独的自动记忆系统。 启用后,每个会话 MEMORY.md 文件也被注入。 文件被截断为 200行 or 25,000 字节,以先触发者为准,如果被截断,则会附加警告。 这可以防止不断增长的内存文件使每个请求变得臃肿。
// memdir/memdir.ts
export const MAX_ENTRYPOINT_LINES = 200
export const MAX_ENTRYPOINT_BYTES = 25_000
The env_info_simple 部分是由组装的 computeSimpleEnvInfo()。 它为模型提供了其运行时上下文的快照。
// Produced output (representative)
# Environment
You have been invoked in the following environment:
- Primary working directory: /Users/alice/myproject
- Is a git repository: true
- Platform: darwin
- Shell: zsh
- OS Version: Darwin 25.3.0
- You are powered by the model named Claude Sonnet 4.6. The exact model ID is claude-sonnet-4-6.
- Assistant knowledge cutoff is August 2025.
- The most recent Claude model family is Claude 4.5/4.6 ...
- Claude Code is available as a CLI in the terminal, desktop app ...
- Fast mode for Claude Code uses the same Claude Opus 4.6 model with faster output ...
Shell检测解析 process.env.SHELL 提取名称。 在 Windows 上,它附加了一个注释以首选 Unix shell 语法(/dev/null not NUL,路径中的正斜杠)。 知识截止日期根据模型规范名称进行硬编码
getKnowledgeCutoff() — 每个模型的发布都需要
// @[MODEL LAUNCH] update.
isUndercover() 激活后,所有模型名称引用都会从环境部分中删除。 这可以防止内部模型 ID 泄露到公共提交、PR 或屏幕截图中。
当连接 MCP 服务器时, getMcpInstructions() 迭代
ConnectedMCPServer 对象并从任何暴露的对象组装一个块 instructions field.
// constants/prompts.ts
function getMcpInstructions(mcpClients) {
const withInstructions = mcpClients
.filter(c => c.type === 'connected')
.filter(c => c.instructions)
const blocks = withInstructions.map(c => `## ${c.name}\n${c.instructions}`).join('\n\n')
return `# MCP Server Instructions\n\n...\n\n${blocks}`
}
这就是将“重要:在使用任何 chrome 浏览器工具之前,您必须首先使用 ToolSearch 加载它们”之类的文本注入到系统提示符中 - 它逐字来自 MCP 服务器的 instructions 字段,而不是来自 Claude Code 自己的代码。
还有一个实验 mcpInstructionsDelta 路径:启用该功能后,指令将作为 持续依恋 对象而不是每次都提示重新注入到系统中。 这可以避免当延迟连接的 MCP 服务器强制使用新的系统提示哈希时发生提示缓存崩溃。
子代理商推出 AgentTool 不经过 getSystemPrompt()。 他们以呼叫者通过的任何系统提示开始 - 通常 DEFAULT_AGENT_PROMPT.
enhanceSystemPromptWithEnvDetails() 然后附加环境上下文和特定于代理的注释。
// The agent default prompt — what every subagent starts with
export const DEFAULT_AGENT_PROMPT = `You are an agent for Claude Code, Anthropic's official CLI for Claude. Given the user's message, you should use the tools available to complete the task. Complete the task fully—don't gold-plate, but don't leave it half-done. When you complete the task, respond with a concise report covering what was done and any key findings — the caller will relay this to the user, so it only needs the essentials.`
// Notes appended by enhanceSystemPromptWithEnvDetails()
`Notes:
- Agent threads always have their cwd reset between bash calls — use absolute file paths.
- In your final response, share file paths (always absolute, never relative) ...
- For clear communication the assistant MUST avoid using emojis.
- Do not use a colon before tool calls.`
computeEnvInfo() (完整版本,不是 computeSimpleEnvInfo) 在这里使用 - 它添加了 uname -sr 输出通过 getUnameSR() 并将环境块包装在 <env> XML 标签而不是主会话中使用的更简单的项目符号列表格式。
CLAUDE_CODE_SIMPLE=1
设置此环境变量会激活三行系统提示符 - 没有说明,没有 CLAUDE.md,没有工具指南,只有 CWD 和日期。 对于对原始模型功能进行基准测试非常有用,无需 Claude Code 的提示开销。
if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
return [`You are Claude Code, Anthropic's official CLI for Claude.\n\nCWD: ${getCwd()}\nDate: ${getSessionStartDate()}`]
}
主动/KAIROS 模式
When feature('PROACTIVE') or feature('KAIROS') 已启用且主动模式处于活动状态, getSystemPrompt() 返回简短的自主代理身份提示,而不是完整的交互式会话提示。 它包括内存、环境信息、MCP 指令和主动部分,但没有交互式编码任务指南。
return [
`\nYou are an autonomous agent. Use the available tools to do useful work.\n\n${CYBER_RISK_INSTRUCTION}`,
getSystemRemindersSection(),
await loadMemoryPrompt(),
envInfo,
getLanguageSection(settings.language),
getMcpInstructionsSection(mcpClients),
getScratchpadInstructions(),
getProactiveSection(),
].filter(s => s !== null)
要点
buildEffectiveSystemPrompt()实现严格的优先级瀑布:覆盖 > 协调器 > 代理 > 自定义 > 默认,其中appendSystemPrompt总是追加。- 提示分为静态部分(全局缓存范围,对所有用户相同)和动态尾部分(特定于会话)。 界碑
__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__是接缝。 - 动态部分由命名注册表管理。 大多数内容都会在会议中被记住; 仅有的
mcp_instructions是不稳定的,因为服务器在会话中连接。 - CLAUDE.md 文件在四个范围内加载(托管 → 用户 → 项目 → 本地),靠近 CWD 的文件通过最后出现而具有更高的优先级。
- The
@include指令让 CLAUDE.md 文件组成其他文件。 前题paths将指令门控到特定的文件模式。 - 子代理绕过
getSystemPrompt()完全地,从DEFAULT_AGENT_PROMPT然后用环境细节进行增强。 - 存在多个逃生舱口:
CLAUDE_CODE_SIMPLE=1对于存根提示,主动模式用于自主代理身份,卧底模式用于删除所有模型名称引用。 - Every
// @[MODEL LAUNCH]注释标记了在每个新的 Claude 模型版本中需要人工关注的代码。
知识检查
overrideSystemPrompt,系统最终会采用哪条路径?overrideSystemPrompt 处在优先级瀑布的最顶层。一旦命中,它会短路整个解析流程,其他来源都不会再进入结果。mcp_instructions 被定义成不缓存的动态 section?CLAUDE.md 的加载优先级里,哪个来源通常拥有最高有效优先级?__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__ 的工程意义是什么?