
Claude Code 是如何组织系统提示词和上下文的
看了 claude-code 的系统提示词源码,我发现它不是靠一段简单的 system prompt 来提醒模型选对工具,而是靠一整套分层控制面在提前塑形。
看了 claude-code 的系统提示词源码,我有一个核心判断:它不是靠一段简单的 system prompt 来"提醒模型选对工具",而是靠一整套分层控制面在提前塑形。
这套控制面至少有六层。
第一层:静态 System Prompt
默认 prompt 在 src/constants/prompts.ts:445-578 里组装,里面专门有 # Using your tools 段落,明确要求"有专用工具就不要用 Bash",并给出读文件、编辑文件、搜索文件、并行调用工具等全局规则。
第二层:User Context 和 System Context 的分流
src/context.ts:116-188 只负责产出上下文数据;真正怎么注入是在 src/utils/api.ts:437-474。这里把 systemContext 追加到 system prompt 尾部,把 userContext 作为一个 synthetic user message,并包成 <system-reminder>。
这很关键。repo 指令、memory、日期之类的信息按"语义角色"分开投喂,不是简单塞进同一个字符串。
第三层:Tool 自己的 Prompt/Description
工具 schema 发给模型之前,会通过 src/utils/api.ts:119-266 把每个 tool 的 prompt() 结果渲染成 API description。工具选择主要靠每个工具自己的 affordance 定义,不是靠顶层 prompt 猜。
典型例子:
src/tools/GrepTool/prompt.ts:6-17src/tools/FileReadTool/prompt.ts:27-48src/tools/BashTool/prompt.ts:42-160
第四层:Reminder/Attachment
它们不是只在会话开始时加一次,而是:
- 初始用户输入阶段注入:
src/utils/processUserInput/processUserInput.ts:495-513 - 工具执行后的下一轮继续前再次注入:
src/query.ts:1541-1593
这些 attachment 最后在 src/utils/messages.ts:3660-4290 转成模型可见的 <system-reminder> 消息。这里会动态提醒 plan mode、auto mode、todo/task、skill、deferred tools、MCP instructions、compaction 等。
第五层:工具可见性本身
src/utils/toolSearch.ts:154-198 和 src/services/api/claude.ts:1105-1233 不只是告诉模型"该用哪个工具",而是直接决定哪些工具此刻可见、哪些要 defer、哪些要通过 ToolSearch 再发现。这层是最强的先验——模型看不见的工具就不会选。
第六层:结构和缓存稳定性
src/constants/prompts.ts:115 放了 SYSTEM_PROMPT_DYNAMIC_BOUNDARY,src/utils/api.ts:321-435 负责把静态/动态 prompt block 拆开,src/constants/systemPromptSections.ts:17-57 负责 section 级缓存,src/utils/toolSchemaCache.ts:3-8 负责 tool schema 缓存。
更细的一层是 src/utils/messages.ts:1808-1950,会把 <system-reminder> 重新 wrap、smoosh、relocate,避免上下文拓扑本身诱发坏模式。
这套上下文构建的哲学
把"工具选择"当成一个持续控制回路,不是一次性提示词。
- 稳定规则放静态 prompt,易变信息放 reminder/delta,不让动态内容污染整个 prompt 前缀
- Tool description 才是最主要的 routing surface,顶层 system prompt 只是总则
- 动态能力变化要通过"增量提醒"或"能力列表变化"告诉模型,而不是每轮重写整段 prompt
- 不只优化 token 成本,也优化行为稳定性。Claude Code 把 prompt bytes 当成架构资产,而不是字符串拼接产物
- System 注入的信息会显式打
<system-reminder>标签,并在消息归一化时特殊处理,避免模型把它误学成普通 user turn 模式
对比:常见的简化做法
很多 agent 系统的上下文构建还停留在"单字符串 system prompt + repo context 拼接 + tool modelDescription"这一层,缺少真正的分层上下文系统。
具体表现:
- 把固定 system prompt 和
repoContext.content直接拼成一个字符串 - 虽然有
stable和volatile的概念,但还只是标签,没有真正驱动不同注入策略 - Provider 层只接受单个
systemPrompt?: string,system 层还是"扁平字符串"模型 - 工具选择主要靠两处:app 层那几句总则 + 每个工具的
MODEL_DESCRIPTION
缺的不是"更多文案",而是缺一个独立的 reminder plane 和 request-time context assembly。
升级建议
如果你正在构建类似的 agent 系统,可以考虑这些方向:
架构层面
- 别再把上下文建模成一个最终字符串,改成 provider-neutral 的
PromptEnvelope - 把
ContextSection思路补完整:加channel(区分 system、user-reminder、post-tool-reminder)、保留stability、再加cadence或injectionPolicy - 让
AgentSession在每次 provider 请求前做 context assembly,而不是 app 层先拼好一整段字符串再塞进去
Context 使用
CLAUDE.md链路更适合进稳定的 repo-instruction channelcwd/repoRoot/env适合进 session 级 system sectioncurrent-date、git snapshot更适合进 volatile reminder,而不是和 durable rules 混在一个字符串里
工具层
- 全局"用哪个工具"的总则不要硬编码,而应该从 tool registry 生成
- 让 tool 定义显式 routing metadata,比如
preferredOver、fallbackOnly、primaryUseCases、counterExamples
Reminder 策略
- 错误恢复 reminder:工具参数连续失败后,给模型一个纠偏 reminder
- 能力变化 reminder:以后加 MCP / dynamic tools 时,用 delta 提示而不是重写全 prompt
- 模式 reminder:以后如果引入 plan mode / auto mode,直接复用同一套机制
一句话概括:Claude Code 的强点不是 prompt 更长,而是它把"静态规则、动态提醒、工具 affordance、工具可见性、上下文结构稳定性"拆成了不同层。要升级的话,不是继续往 buildSystemPrompt() 里加句子,而是把上下文构建从"字符串拼接"升级成"分层装配"。


