System Prompt 三层组装与 CLAUDE.md 四级覆盖——上下文管理的最后一公里

《Claude Code 架构解密》精读笔记 · 第12篇
覆盖章节:第7章后半(7.5-7.8, p.184-198)
主题:系统 Prompt 组装管线、CLAUDE.md 覆盖链、上下文管理设计模式


导语:Prompt 不是一段文本,而是一条管线

上一篇文章,我们拆解了 Claude Code 面对上下文窗口极限的四层压缩策略——从零成本的 API 原生压缩到 LLM 驱动的 Full Compact。但压缩只是"对话历史"这端的问题,上下文管理还有另一端:每次 API 调用时注入的系统 Prompt

你可能会想:系统 Prompt 不就是一段固定的文本吗?有什么好讲的?

在 Claude Code 的架构中,系统 Prompt 远不止"一段文本"——它是一条三层组装管线,精确映射 API 缓存粒度;它的第三层是一个四级覆盖链,从企业到个人层层叠加;当多层指令冲突时,仲裁者不是代码,而是 LLM 本身。

这不是"Prompt Engineering",这是 Prompt Architecture


一、架构定位:三层管线在上下文管理中的位置

┌─────────────────────────────────────────────────────────┐
│                    上下文管理全景                          │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌──────────────┐    ┌──────────────┐    ┌───────────┐ │
│  │ 7.2-7.4      │    │ 7.5          │    │ 7.6       │ │
│  │ 四层压缩策略  │    │ System Prompt│    │ CLAUDE.md │ │
│  │ (对话历史端)  │    │ 三层组装管线  │    │ 四级覆盖链 │ │
│  │              │    │ (行为指令端)  │    │ (用户定制) │ │
│  └──────────────┘    └──────────────┘    └───────────┘ │
│         ↓                   ↓                  ↓        │
│  ┌──────────────────────────────────────────────────┐   │
│  │              7.7 设计模式提炼                      │   │
│  │  后台Agent记忆 | 内容引用分离 | 缓存感知分层 |      │   │
│  │  覆盖链配置                                       │   │
│  └──────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

三层管线解决的核心问题:如何在保持缓存命中率的同时,注入会话级动态内容和用户级定制指令?


二、核心点拆解

2.1 三层组装管线——缓存优化驱动的分层架构

buildEffectiveSystemPrompt() 是组装管线的入口,接收默认 Prompt 数组和用户指令层,输出品牌类型 SystemPrompt

三层划分不是随意的,而是精确映射 Anthropic API 的缓存粒度

层级 变更频率 缓存策略 对应 API 行为
静态层 几乎不变(版本更新才变) cacheScope: 'global',跨组织缓存 Prompt Cache 前缀命中
动态层 会话内稳定 会话级缓存,少数每轮重算 同会话内缓存命中
用户指令层 按用户/项目不同 无缓存(完全个性化) 每次全量传输
第 1 层:静态内容——不变的行为准则

静态层是整个 Prompt 的"基岩",由 7 个有序章节组成:

序号 内容概要 架构意图
身份与安全 身份定义 + 网络安全指令 划定身份边界和安全底线
系统环境 工具执行、输出格式、自动压缩说明 告知模型自身运行环境的约束
任务执行 编码风格、用户协助原则 控制代码质量和交互行为
风险操作 破坏性操作确认要求 安全架构的 Prompt 层
工具选择 优先使用专用工具而非 Bash 避免"Bash 万能化"退化
格式规范 输出风格 UX 一致性
输出效率 简洁性要求 降低 Token 消耗

每个章节标注 cacheScope: 'global',意味着对所有用户完全相同。当 Anthropic 的 Prompt Cache 命中时,这部分的 Token 计算成本为零。

Prompt 层安全 vs 代码层安全——一个值得深入的设计决策:

  • 代码层安全(权限系统、沙箱、命令黑名单)是硬性约束——即使模型"想"执行 rm -rf /,代码也会拦截
  • Prompt 层安全软性引导——让模型"不想"执行危险操作,减少权限弹窗的频率

两者构成纵深防御。Prompt 中的破坏性操作列举与 BashTool 中的硬性黑名单形成互补——"软 + 硬"双层设计降低了权限弹窗频率,改善了用户体验。

静态分隔标记:第 1 层和第 2 层之间用 SYSTEM_PROMPT_DYNAMIC_BOUNDARY 分隔。这个标记被 API 调用层的 splitSysPromptPrefix 函数使用,将 Prompt 分割为 cacheable prefix 和 non-cacheable suffix,直接映射到 cache_control 字段。

第 2 层:动态章节——注册表与缓存策略

动态章节层是三层管线中最精巧的设计。它采用注册表模式——每个章节通过工厂函数注册,运行时按名称解析:

type SystemPromptSection = {
  name: string
  cacheBreak: boolean  // 是否每轮重算
}

两个工厂函数的区别仅在于 cacheBreak 字段:

工厂函数 cacheBreak 适用场景
systemPromptSection(name, compute) false 会话内稳定的内容,如技能列表、MCP 指令
DANGEROUS_uncachedSystemPromptSection(name, compute, reason) true 每轮都可能变化的内容,如当前时间

DANGEROUS_ 前缀的治理智慧——这不是危言耸听,而是一个刻意的架构治理模式:

  1. 承认成本:DANGEROUS_ 前缀让代码审查者立即意识到这个章节会破坏缓存
  2. 文档化理由_reason 参数虽然运行时不使用,但强制在代码中留下"为什么需要每轮重算"的理由

这是 Claude Code 中反复出现的设计哲学:通过 API 设计使"错误的事情难以做到"。如果一个章节不需要每轮重算,没有开发者会主动选用带 DANGEROUS_ 前缀的函数——从而保护了 Prompt Cache 的命中率。

并行解析流程:所有动态章节通过 Promise.all 并行计算,前提是各章节之间没有数据依赖——环境信息、技能列表、MCP 指令都是独立获取的。“约束产生效率”:正是因为章节间无依赖的设计约束,才使得并行解析成为可能。

缓存失效的四个触发点

触发场景 原因
/clear 命令 用户主动重置会话
/compact 命令 压缩后 Prompt 需要反映新状态
进入/退出 Worktree CWD 变化导致环境信息过时
会话恢复 恢复时需重建完整状态

一个细节:清理缓存时会同步调用 clearBetaHeaderLatches()——确保功能开关在新上下文中重新评估,避免 Prompt 内容和 API 请求头之间的不一致。这种"关联失效"体现了系统一致性的严谨追求。

动态章节清单:

章节 缓存策略 内容示例
会话指导 会话内缓存 按会话类型注入的特定行为指导
记忆 会话内缓存 memdir 加载的自动记忆文件
环境信息 会话内缓存 CWD、Git 状态、平台、Shell、模型
语言 会话内缓存 用户语言偏好
输出样式 会话内缓存 输出风格配置
MCP 指令 会话内缓存 MCP 服务器注入的行为指令
暂存区 会话内缓存 暂存目录使用说明
Token 预算 会话内缓存 Token 预算说明(FeatureGate 控制)
第 3 层:用户指令层(CLAUDE.md)

第三层是整个管线中最贴近用户的部分,核心载体是 CLAUDE.md 文件——一种 Markdown 格式的配置文件,用户通过自然语言为 Agent 设定行为准则。详见下文 2.2 节。

2.2 CLAUDE.md 四级覆盖链——从企业到个人的配置层叠

为什么需要多级配置?

一个场景:一家企业有 100 名开发者在使用 Claude Code。企业安全团队希望所有人的 Agent 都遵守"不得将代码推送到公共仓库"的规则;每个开发者又有自己的编码风格偏好;某个项目组有特定的代码规范;而某个开发者正在本地实验新的 lint 规则,不希望影响团队。

四级加载层级(优先级低→高):

/etc/claude-code/CLAUDE.md     ← Managed(企业级,管理员设定)
~/.claude/CLAUDE.md            ← User(个人全局偏好)
<repo>/CLAUDE.md               ← Project(项目级规范)
<repo>/.claude/CLAUDE.md       ← Project(隐藏目录,等效)
<repo>/.claude/rules/*.md      ← Project Rules(条件规则)
<repo>/CLAUDE.local.md         ← Local(本地私有,不入 Git)

这是经典的**覆盖链(Override Chain)**设计模式,与 CSS 的层叠规则异曲同工。

层级 存储位置 版本控制 典型内容
Managed /etc/claude-code/ 由 MDM 工具分发 企业安全策略、合规要求
User ~/.claude/ 不入版本控制 个人编码风格、语言偏好
Project 项目根目录 入 Git,团队共享 项目代码规范、架构约定
Local 项目根目录 .gitignore 排除 本地实验性配置
条件规则——上下文感知的指令注入

.claude/rules/ 目录下,每个 Markdown 文件可以通过 YAML Frontmatter 声明自己适用的文件模式:

paths:
  - "src/components/**/*.tsx"
  - "src/hooks/**/*.ts"
React 组件必须使用函数组件,不使用 class 组件。
所有 hooks 必须以 use 前缀命名。
状态管理优先使用 useReducer 而非 useState(复杂场景)。

条件规则的必要性:前端规范和后端代码完全不同。如果所有规则无条件注入 Prompt,会产生两个问题:

  1. 窗口浪费:不相关的规则占用了宝贵的上下文窗口空间
  2. 遵循度下降:模型面对大量不相关指令时,对关键指令的遵循度会降低

条件规则让 Prompt 只包含与当前任务相关的指令——这与 7.2 节"按需保留"压缩思想一脉相承。

@include 引用机制与安全防护

记忆文件支持 @path 语法引用其他文件,实现配置的模块化组织:

# 项目级 CLAUDE.md
@./coding-standards.md
@./api-conventions.md
@~/personal-preferences.md

引用机制引入了潜在安全风险——循环引用和路径遍历。Claude Code 为此设计了三层防护:

  1. 循环引用防护processedPaths Set 追踪已处理路径,同一文件不会被处理两次
  2. 深度限制MAX_INCLUDE_DEPTH = 5,防止深层嵌套导致的栈溢出
  3. 符号链接处理safeResolvePath 先解析符号链接到真实路径,同时在 processedPaths 中记录原始路径和解析后路径——防止通过符号链接绕过循环检测
内容处理管线

从磁盘文件到最终注入 Prompt 的内容,经过一条完整的处理管线:

读取文件 → 检查扩展名白名单(100+种) → 解析 YAML Frontmatter → 
剥离 HTML 注释(marked Lexer) → 提取 @include 路径 → 
对 AutoMem/TeamMem 应用截断(40000字符) → 递归处理 @include 引用

两个值得关注的细节:

  • 剥离 HTML 注释:使用 marked Lexer 而非简单的正则替换,因为 HTML 注释可能出现在代码块内(应保留)或正文中(应剥离)
  • 截断信任边界MAX_MEMORY_CHARACTER_COUNT = 40000 只应用于自动记忆(AutoMem)和团队记忆(TeamMem),不限制用户手动编写的 CLAUDE.md。用户手写内容假定经过深思熟虑,而自动生成内容可能无限膨胀
覆盖语义与冲突仲裁——全部注入,模型仲裁

如果 Managed 层说"禁止使用 eval()“,而 Project 层说"在测试代码中允许使用 eval()”,最终谁生效?

Claude Code 的回答是:全部注入,由模型仲裁。

四级配置文件的内容全部被注入 Prompt,以标注来源层级的方式并列呈现。模型会看到两条矛盾的指令,并根据上下文(当前正在编辑的是测试文件还是生产代码)做出判断。

为什么不在代码层做硬性优先级覆盖?两个原因:

  1. 自然语言规则难以机器比较——"禁止使用 eval()"和"在测试中允许 eval()"不是简单的键值对,无法用程序判断它们是否冲突
  2. LLM 的上下文理解能力恰好适合处理模糊的优先级仲裁——它可以根据当前文件类型、操作意图综合判断

当然,"软仲裁"也有风险——Claude Code 的应对是在注入时标注层级来源,通过描述性标注影响模型对冲突指令的优先级判断。这是 Prompt 工程的一个精妙应用:通过描述性标注影响模型对冲突指令的优先级判断。

2.3 六级优先级仲裁器

三层管线组装完毕后,还需要面对 SDK 嵌入方、Agent 定义、用户参数等多方指令冲突。buildEffectiveSystemPrompt 中实现了一个六级优先级仲裁器:

级别 典型场景 设计意图
Override SDK 嵌入/自动化 完全控制模型行为,忽略所有默认规则
Coordinator 多 Agent 协调模式 协调器需要与执行者不同的行为模式
Agent (proactive) 自主 Agent 模式 保留默认安全规则,在其上叠加 Agent 指令
Agent (normal) 普通 Agent 任务 Agent 完全定义自己的行为
Custom CLI --system-prompt 开发者实验用
Default + Append 正常交互 标准行为 + 用户追加指令

关键安全决策:Proactive 模式下,Agent Prompt 是追加而非替换默认 Prompt。为什么?默认 Prompt 包含安全准则(如破坏性操作确认要求),如果替换了,Agent 可能在没有用户确认的情况下执行破坏性操作。

延迟加载与条件编译:通过 feature() + require() 实现条件加载——feature() 在构建时由 Bun bundler 评估,如果条件为 false 则整个 require 被消除(dead code elimination)。这避免了加载未启用功能的代码,减小 bundle 体积,同时解决了某些模块间的循环依赖问题。

2.4 上下文窗口约束——Prompt 的物理极限

系统 Prompt 不是可以无限增长的——它受上下文窗口大小的约束。context.ts 负责管理这一物理极限。

窗口大小的决策链

环境变量覆盖 (仅内部用户)
  → 模型名[1m]后缀检测 → 1M tokens
  → getModelCapability() 能力查询
  → Beta Header 检查
  → 实验分组检查
  → 默认值:200,000 tokens

输出 Token 的精巧优化

export const CAPPED_DEFAULT_MAX_TOKENS = 8_000   // 默认上限
export const ESCALATED_MAX_TOKENS = 64_000       // 达到上限后重试的值

根据实际数据,p99 的输出约 4,911 tokens,而默认 max_tokens 如果设为 32K/64K 会过度预留 8-16 倍容量。Claude Code 的策略是"先小后大"——默认 8K,只有当模型实际触达上限时才提升到 64K 重试。99%+ 的请求因此节省了大量预留空间,为对话历史腾出更多上下文窗口。


三、设计模式提炼

纵观第 7 章的压缩策略和系统 Prompt 工程,可以提炼出四个核心设计模式:

模式一:后台 Agent 驱动的会话记忆

问题:长对话压缩中 LLM 摘要不可避免地丢失结构化任务状态。

核心思想:Fork 一个专用子 Agent,在后台周期性提取结构化的会话记忆。

主对话流 (REPL)
  ↓ postSamplingHook
  ↓ extractSessionMemory()
  ↓ 双阈值判断 (tokens > 阈值 AND 工具调用 > 阈值)
  ↓ createSubagentContext()
  ↓ setupSessionMemoryFile()
  ↓ runForkedAgent() — 只允许 FileEditTool,只能编辑 summary.md
  ↓ 完成

设计要点:

  • 双阈值触发(token 10000 + 工具调用 3 次),避免闲聊时浪费资源
  • 工具权限隔离:后台 Agent 只被授权 FileEditTool + 只能编辑指定文件
  • sequential() 并发控制 + Compact 前等待提取完成(15 秒超时)
  • 9 章节结构化模板,总限制 12000 tokens

与 Compact 的协同:Session Memory 优先于传统 Compact 被触发。当 Session Memory 存在时,Compact 操作会使用会话记忆内容作为摘要(而非重新调用 LLM 生成),实现**"零 LLM 成本"的 Compact**。

模式二:内容引用分离

问题:大段代码/日志内联存储导致历史文件膨胀到数百 MB。

核心思想:基于大小阈值的内联-引用分离——小内容直接存储,大内容用 SHA256 哈希引用,指向独立的缓存文件。

// 写入时分流
if (content.length <= 1024) {
  stored = { id, type: 'text', content }           // 小内容内联
} else {
  const hash = sha256(content).slice(0, 16)          // 16字符截断
  await storePastedText(hash, content)               // 存到 paste-cache/
  stored = { id, type: 'text', contentHash: hash }   // 只存引用
}

设计要点:

  • 阈值 1024 字节:太小导致碎片化,太大导致历史膨胀——1KB 是经验平衡点
  • SHA256 取前 16 字符(64bit),冲突概率在单用户场景下可忽略
  • 倒序替换:展开引用时从后往前处理,避免前面的替换改变后面引用的字符偏移量
  • 懒加载语义:历史文件记录引用而非内容,只在实际需要还原时才从 paste-cache 读取

模式三:缓存感知 Prompt 分层

问题:Prompt 包含不同变更频率的内容,混在一起无法利用 API 缓存。

核心思想:按变更频率分层,精确映射缓存策略。

关键技术:三层管线 + DANGEROUS_API 治理 + cache_control 映射 + SYSTEM_PROMPT_DYNAMIC_BOUNDARY 分隔标记。

模式四:覆盖链配置

问题:多级配置需要灵活覆盖,但自然语言规则难以机器比较。

核心思想:多级文件按优先级叠加,模型仲裁冲突。

关键技术:四级层级 + 条件规则(glob 模式)+ @include 引用 + LLM 冲突仲裁。

模式速查表

模式 问题 核心思想 关键技术
后台 Agent 记忆 长对话压缩丢失结构 Fork 子 Agent 后台提取 双阈值触发、工具权限隔离、sequential 并发控制
内容引用分离 大内容内联导致膨胀 小内联、大哈希引用 SHA256 截断、倒序替换、懒加载展开
缓存感知分层 Prompt 含不同频率内容 按频率分层映射缓存 三层管线、DANGEROUS_API 治理、cache_control
覆盖链配置 多级配置灵活覆盖 多级叠加+模型仲裁 四级层级、条件规则、@include 引用

四、横向对比

System Prompt 组装 vs 其他 Agent 框架

维度 Claude Code LangChain OpenAI Assistants
Prompt 来源 三层管线(静态+动态+用户) 代码级拼接 单一 instructions 字段
缓存感知 精确映射 API 缓存粒度 API 级自动缓存
动态章节 注册表模式,并行计算 代码级条件渲染 无动态概念
用户定制 四级覆盖链 + 条件规则 无内置机制 无内置机制
冲突仲裁 LLM 软仲裁 + 层级标注 代码级覆盖 最后写入者胜出
治理模式 DANGEROUS_ API 命名

CLAUDE.md 四级覆盖 vs 其他配置体系

维度 CLAUDE.md ESLint 配置 Git 配置
层级数 4 级(Managed/User/Project/Local) 3 级(Home/Project/Override) 3 级(System/Global/Local)
条件规则 glob 模式 + YAML Frontmatter overrides 字段
冲突处理 全部注入 + LLM 仲裁 靠后优先覆盖 靠后优先覆盖
引用机制 @include + 三层安全防护 extends + plugins includeIf(条件包含)
企业管控 Managed 层 + MDM 分发 共享配置包 系统级模板

核心差异:CLAUDE.md 的"全部注入 + LLM 仲裁"是独一无二的——其他配置体系都用代码级覆盖,而 Claude Code 把模糊冲突交给最擅长理解自然语言的 LLM。


五、实战启示

启示 1:缓存感知是 Prompt 架构的第一性原理

Claude Code 的三层管线不是"为了好看"而分层——每一层都精确映射 API 的缓存粒度。如果你在设计 Agent 系统的 Prompt 体系,第一件事不是写 Prompt 内容,而是搞清楚你的 API 提供商的缓存边界在哪里。

把不变的内容放前面(全局缓存),会话级变化的内容放中间(会话缓存),用户个性化内容放后面(无缓存)——这个"变更频率递增"的排列顺序就是缓存感知的核心。

启示 2:API 命名是最廉价的治理工具

DANGEROUS_ 前缀不需要任何运行时成本,却能在代码审查时立刻引发警觉。在你的系统中,如果某个操作有高成本或高风险,让它"看起来"危险比写文档更有效。

类似的模式:React 的 dangerouslySetInnerHTML、Rust 的 unsafe 块——都在用命名传递意图。

启示 3:覆盖链的关键不是"谁覆盖谁",而是"冲突怎么处理"

四级覆盖链的创新不在层级本身(CSS/Git/ESLint 都有多级配置),而在于冲突仲裁策略

  • 代码级覆盖(靠后优先)适合结构化配置,但对自然语言规则无能为力
  • LLM 仲裁适合模糊冲突,但不是确定性的——需要层级标注辅助

实践建议:对于安全关键规则,在代码层做硬性执行(如权限系统的 deny 规则);对于风格偏好等软性规则,交给 LLM 仲裁——硬约束代码化,软约束 Prompt 化

启示 4:输出 Token 的"先小后大"策略

默认 8K、触达后升级 64K——这不是一个简单的参数选择,而是对99% vs 1% 场景的精确建模。p99 输出 4,911 tokens 意味着 32K/64K 的默认值是在为 1% 的极端场景浪费 8-16 倍预留空间。

通用原则:为常见场景优化默认值,为极端场景准备升级路径。过度预留是上下文窗口的隐形杀手。


六、第7章全章总结:上下文管理的核心智慧

回望整个第7章,从四层压缩策略到三层 Prompt 管线到四级覆盖链,一条主线贯穿始终:

分层 + 渐进——不试图用一个万能方案解决所有问题,而是按照不同维度(成本、变更频率、信任层级)将问题分解,每层用最适合的策略处理。

  • 压缩策略:零成本 → 零 LLM → LLM 驱动,渐进升级
  • Prompt 管线:全局缓存 → 会话缓存 → 无缓存,变更频率递增
  • 配置覆盖:企业 → 个人 → 项目 → 本地,信任层级递减

四者协同:压缩解决"对话太长",Session Memory 在压缩中保留结构化知识,Prompt 管线确保每次调用携带正确行为指令,CLAUDE.md 覆盖链让用户精细控制 Agent 行为——让一个有限窗口的 LLM 能够支撑无限长度的工作会话。


下期预告

第 13 篇,我们将进入第8章——安全纵深防御,这是 Claude Code 安全架构的集大成之作:六层防御模型总览、OS 级沙箱、命令注入防护、路径遍历防护。当 AI 拥有了执行能力,安全就不再是一个功能,而是一个体系。

第13篇预告:沙箱里的自由——Claude Code 六层纵深防御体系全解


本篇为《Claude Code 架构解密》精读笔记第12篇,覆盖第7章后半(7.5-7.8)。
上一篇:[第11篇] Context 的生死抉择——四层压缩、截断算法与 Session Memory
系列持续更新中,关注获取更多精读内容。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐