手撕 Claude Code-2:全局 Context Prompt

3 阅读5分钟

第 2 章:全局 Context Prompt — 系统提示的构建机制

源码位置:src/utils/systemPrompt.tssrc/utils/attachments.tssrc/constants/prompts.ts


2.1 为什么系统提示很重要?

在 LLM 应用中,系统提示决定了 AI 的身份、能力边界和行为规范。Claude Code 的系统提示并非一成不变——它根据当前模式(普通模式 / Coordinator 模式 / Proactive 模式 / 自定义 Agent 模式)动态构建,并在每一轮 API 调用前组装。


2.2 系统提示的五个来源

优先级(从高到低):

  0. Override 提示     ← loop 模式等特殊场景,完全替换所有其他提示
  1. Coordinator 提示  ← 自主代理协调器模式(环境变量 CLAUDE_CODE_COORDINATOR_MODE=12. Agent 提示        ← mainThreadAgentDefinition 定义的自定义代理
     ├── Proactive 模式:Agent 提示 "追加"Default 之后
     └── 普通模式:Agent 提示 "替换" Default
  3. Custom 提示       ← 用户通过 --system-prompt CLI 参数传入
  4. Default 提示      ← Claude Code 内置的标准提示
  + Append 提示        ← 始终追加在末尾(Override 除外)

2.3 核心函数:buildEffectiveSystemPrompt()

源码位置:src/utils/systemPrompt.ts:41

export function buildEffectiveSystemPrompt({
  mainThreadAgentDefinition,
  toolUseContext,
  customSystemPrompt,
  defaultSystemPrompt,
  appendSystemPrompt,
  overrideSystemPrompt,
}: {
  mainThreadAgentDefinition: AgentDefinition | undefined
  toolUseContext: Pick<ToolUseContext, 'options'>
  customSystemPrompt: string | undefined
  defaultSystemPrompt: string[]
  appendSystemPrompt: string | undefined
  overrideSystemPrompt?: string | null
}): SystemPrompt {
  
  // 优先级 0:Override(直接返回,忽略所有其他提示)
  if (overrideSystemPrompt) {
    return asSystemPrompt([overrideSystemPrompt])
  }
  
  // 优先级 1:Coordinator 模式
  // 注意:使用 lazy require 避免循环依赖
  if (
    feature('COORDINATOR_MODE') &&
    isEnvTruthy(process.env.CLAUDE_CODE_COORDINATOR_MODE) &&
    !mainThreadAgentDefinition
  ) {
    const { getCoordinatorSystemPrompt } = require('../coordinator/coordinatorMode.js')
    return asSystemPrompt([
      getCoordinatorSystemPrompt(),
      ...(appendSystemPrompt ? [appendSystemPrompt] : []),
    ])
  }

  // 计算 Agent 提示(如果有自定义 Agent 定义)
  const agentSystemPrompt = mainThreadAgentDefinition
    ? isBuiltInAgent(mainThreadAgentDefinition)
      ? mainThreadAgentDefinition.getSystemPrompt({ toolUseContext: { options: toolUseContext.options } })
      : mainThreadAgentDefinition.getSystemPrompt()
    : undefined

  // 优先级 2(Proactive 模式):Agent 提示追加到 Default 之后
  if (agentSystemPrompt && isProactiveActive_SAFE_TO_CALL_ANYWHERE()) {
    return asSystemPrompt([
      ...defaultSystemPrompt,
      `\n# Custom Agent Instructions\n${agentSystemPrompt}`,
      ...(appendSystemPrompt ? [appendSystemPrompt] : []),
    ])
  }

  // 优先级 2/3/4:Agent > Custom > Default,最后加 Append
  return asSystemPrompt([
    ...(agentSystemPrompt
      ? [agentSystemPrompt]
      : customSystemPrompt
        ? [customSystemPrompt]
        : defaultSystemPrompt),
    ...(appendSystemPrompt ? [appendSystemPrompt] : []),
  ])
}

2.4 决策树图

调用 buildEffectiveSystemPrompt()
              │
              ▼
    overrideSystemPrompt 存在?
         │         │
        YES        NO
         │          │
         ▼          ▼
    [Override]  COORDINATOR_MODE 环境变量?
                     │            │
                    YES           NO
                     │            │
                     ▼            ▼
               [Coordinator]  mainThreadAgentDefinition 存在?
                                   │              │
                                  YES             NO
                                   │              │
                                   ▼              ▼
                          isProactiveActive()?  customSystemPrompt 存在?
                               │       │            │         │
                              YES      NO           YES       NO
                               │       │             │        │
                               ▼       ▼             ▼        ▼
                          [Default  [Agent只有]  [Custom]  [Default]
                          + Agent]
                               │       │             │        │
                               └───────┴─────────────┴────────┘
                                                │
                                                ▼
                                   + appendSystemPrompt(如果有)

2.5 Attachment Messages

除了系统提示,Claude Code 还向每轮 API 调用注入动态上下文。附件消息类型为 AttachmentMessage,在 normalizeMessagesForAPI() 时转换为 API 格式(通常是 <system-reminder> 包裹的 user message)。

getAttachmentMessages() 实际管理的附件类型超过 50 种,按触发场景分为以下几类(src/utils/attachments.ts:743):

用户输入相关(每次用户提交时处理)

附件类型触发条件
skill_listing有新 skill 尚未告知 LLM(sentSkillNames 判断)
skill_discoveryEXPERIMENTAL_SKILL_SEARCH 开启时,语义搜索结果
nested_memory用户 @-mention 文件时,自动加载该文件所在目录的条件 CLAUDE.md 规则
agent_mentions用户消息中提及了 @agent

全局 Delta(有变化才注入,避免重复 token)

附件类型触发条件说明
deferred_tools_delta模型支持工具引用 + 工具搜索功能开启告知 Claude 还有哪些工具可通过 ToolSearch 发现,不把所有工具塞入 tools 参数
agent_listing_delta可用 Agent 定义发生变化告知 Claude 可以调用哪些 subagent_type
mcp_instructions_deltaMCP 服务器连接后首次注入MCP 服务器的使用说明
date_change日期跨天更新当前日期
plan_mode / plan_mode_exitPlan 模式进入/退出
todo_reminder / task_reminder有未完成任务提醒 Claude 当前待办
teammate_mailboxCoordinator 模式下有新邮件

主线程专属(IDE 集成等)

附件类型触发条件
selected_lines_in_ideIDE 中有选中代码
opened_file_in_ideIDE 中打开了文件
critical_system_reminder有需要强提醒的系统消息

附件的关键设计

  • Delta 模式:全局类附件只在内容发生变化时才注入,避免重复消耗 token
  • AttachmentMessage 容器:统一的内部数据结构,不直接对应 API 格式;normalizeMessagesForAPI() 在每次 API 调用前将其转换
  • relevant_memories 的异步注入:CLAUDE.md 相关记忆通过 startRelevantMemoryPrefetch() 预取,在工具执行后消费(见第 1 章)

2.6 userContext 与 CLAUDE.md

userContext{ [k: string]: string } 键值对,由 getUserContext() 构建,包含两项:

return {
  ...(claudeMd && { claudeMd }),            // CLAUDE.md 合并内容(见下)
  currentDate: `Today's date is ${getLocalISODate()}.`,
}

userContext 不是 systemPrompt 的一部分,而是在 API 调用时通过 prependUserContext()<system-reminder> user message 的形式插入到 messages 列表最前面(src/query.ts:660)。

CLAUDE.md 记忆文件的加载顺序

getMemoryFiles() 按以下顺序加载(src/utils/claudemd.ts:803):

1. Managed(企业策略)
   /etc/claude-code/CLAUDE.md
   ~/.claude-code/rules/*.md

2. User(用户全局,若 userSettings 未禁用)
   ~/.claude/CLAUDE.md
   ~/.claude/rules/*.md

3. Project(从 Git 根目录向下遍历到 CWD,每个目录中按序)
   CLAUDE.md              ← 检入代码库的项目指令
   .claude/CLAUDE.md
   .claude/rules/*.md
   CLAUDE.local.md        ← 本地私有指令,不检入代码库

Git worktree 特殊处理:从 nested worktree 运行时,跳过主仓库的 Project 级文件,避免重复加载。

filterInjectedMemoryFiles 的实际作用

文档原来说"过滤已注入的(避免重复)"是错的。实际逻辑是:

export function filterInjectedMemoryFiles(files: MemoryFileInfo[]): MemoryFileInfo[] {
  const skipMemoryIndex = getFeatureValue_CACHED_MAY_BE_STALE('tengu_moth_copse', false)
  if (!skipMemoryIndex) return files          // 默认:返回全部文件
  return files.filter(f => f.type !== 'AutoMem' && f.type !== 'TeamMem')
}

默认情况下不过滤任何文件。只有 tengu_moth_copse feature flag 开启时,才过滤掉 AutoMem 和 TeamMem 类型(这两类通过 relevant_memories attachment 单独异步注入)。

条件规则(nested_memory)

当用户 @-mention 一个文件时,Claude Code 自动查找该文件所在目录的 CLAUDE.md,将其中匹配当前场景的条件规则作为 nested_memory attachment 注入。这是 CLAUDE.md "子目录级生效"的实现机制。


2.7 systemContext 与 git 状态

userContext 并列,systemContextgetSystemContext() 构建(src/context.ts:116),包含:

return {
  ...(gitStatus && { gitStatus }),       // 当前 git 工作区状态(可选)
  ...(cacheBreaker && { cacheBreaker }), // 缓存破坏器,ant-only(可选)
}

systemContext 的注入方式与 userContext 不同——通过 appendSystemContext() 追加到 systemPrompt 数组末尾(src/query.ts:449),成为 system prompt 的一部分:

// src/utils/api.ts
export function appendSystemContext(
  systemPrompt: SystemPrompt,
  context: { [k: string]: string },
): string[] {
  return [
    ...systemPrompt,
    Object.entries(context)
      .map(([key, value]) => `${key}: ${value}`)  // 格式:"gitStatus: ..."
      .join('\n'),
  ].filter(Boolean)
}
userContextsystemContext
包含内容CLAUDE.md + 当前日期git 状态 + 缓存破坏器
注入方式prependUserContext() → messages 最前的 <system-reminder>appendSystemContext() → 追加到 systemPrompt 末尾
缓存memoize,整个会话只加载一次memoize,整个会话只加载一次

2.8 系统提示的 Token 分析

上下文构建完成后,Claude Code 会分析 token 使用情况:

// src/utils/contextAnalysis.ts
analyzeContext(messages, systemPrompt)
// → tokenStatsToStatsigMetrics()  用于分析上报

这为 Auto Compaction(自动压缩,第 7 章)提供判断依据。


2.9 Coordinator 模式系统提示

Coordinator 模式是 Claude Code 的"自主代理编排"模式,此时系统提示完全不同:

// src/coordinator/coordinatorMode.ts
export function getCoordinatorSystemPrompt(): string {
  // 返回协调器专用提示:
  // - 如何分配任务给 worker 代理
  // - 如何通过 mailbox 通信
  // - 工具集限制(ASYNC_AGENT_ALLOWED_TOOLS)
  // - 任务完成标准
}

export function isCoordinatorMode(): boolean {
  return feature('COORDINATOR_MODE') && 
         isEnvTruthy(process.env.CLAUDE_CODE_COORDINATOR_MODE)
}

Coordinator 也有自己的 Worker 上下文注入:

getCoordinatorUserContext()
// 将可用的 worker 工具列表注入到 user 消息中
// 让 coordinator 知道它能分配哪些能力给 worker

2.10 系统提示缓存优化

Claude API 支持 Prompt Cache(提示缓存)。Claude Code 通过以下方式最大化缓存命中:

  1. 系统提示内容稳定:尽量不在每次循环中改变系统提示内容
  2. Delta 附件模式:只在有变化时才追加新内容,避免整个消息列表发生变化
  3. Fork Subagent 字节级一致:子代理创建时传递"已渲染"的系统提示字节,而不是重新调用 getSystemPrompt()(可能因 GrowthBook 状态变化而产生不同结果)

源码位置:src/tools/AgentTool/forkSubagent.ts:56

// FORK_AGENT 定义:
// The getSystemPrompt here is unused: the fork path passes
// `override.systemPrompt` with the parent's already-rendered system prompt
// bytes, threaded via `toolUseContext.renderedSystemPrompt`. 
// Reconstructing by re-calling getSystemPrompt() can diverge (GrowthBook 
// cold→warm) and bust the prompt cache; threading the rendered bytes is 
// byte-exact.
export const FORK_AGENT = {
  // ...
  getSystemPrompt: () => '',  // 不使用,由 override 传递父代理的已渲染提示
}

小结

机制作用源码位置
buildEffectiveSystemPrompt()按优先级组合系统提示src/utils/systemPrompt.ts:41
userContextCLAUDE.md + 当前日期,以 <system-reminder> 插入 messages 最前src/context.ts:155
systemContextgit 状态 + 缓存破坏器,追加到 systemPrompt 末尾src/context.ts:116
Attachment Messages动态注入上下文(50+ 类型,按 Delta 模式避免重复)src/utils/attachments.ts:743
CLAUDE.md 记忆文件多层级加载(managed → user → project),通过 userContext 注入src/utils/claudemd.ts:803
nested_memory@-mention 文件时自动加载该目录的条件规则src/utils/attachments.ts
Coordinator 提示自主代理编排模式src/coordinator/coordinatorMode.ts
Prompt Cache 优化Fork subagent 传递已渲染字节,保证字节级一致src/tools/AgentTool/forkSubagent.ts:56