System Prompt 工程:800 行提示词背后的设计决策
如果你以为 System Prompt 就是"给 AI 设定一个人设",那 Claude Code 的 system prompt 会颠覆你的认知。
它不是几句话。它是一个上千行的模板引擎,由多个层次组成,每一层负责不同的事:身份定义、行为约束、工具说明、项目上下文、记忆注入、技能加载。有些层是静态的(每次一样),有些层是动态的(根据项目、会话、用户偏好实时注入)。
ChatGPT 里你写的 “You are a helpful assistant” 是 System Prompt 的 Hello World。Claude Code 里的 System Prompt 是生产级应用。
为什么 System Prompt 这么长?
先回答一个直觉性问题:提示词不是越短越好吗?少占上下文,少花钱,少干扰。
对普通对话而言,是的。但 Claude Code 不是普通对话——它是一个在陌生环境里执行危险操作的自主代理。System Prompt 需要回答的问题远比"你是谁"复杂:
- 你的首要目标是什么?(帮用户写代码,不是聊天)
- 你可以用什么工具?(工具定义列表)
- 哪些操作需要确认?(权限边界)
- 这个项目的约定是什么?(CLAUDE.md 注入)
- 用户之前告诉过你什么?(记忆注入)
- 你有什么专业技能?(Skill 注入)
- 出了错怎么办?(错误处理策略)
- 不确定时怎么办?(提问优先于猜测)
每个问题都需要一段明确的指令。这些指令叠加起来,就是一个长 prompt。
关键不是"长",而是结构。800 行无结构的文本是噪声,800 行有结构的指令是操作系统内核。
分层架构:从静态到动态
Claude Code 的 system prompt 可以拆成四层,从最内层(最稳定)到最外层(最易变):
第一层:核心身份(Core Identity)
这是最内层,几乎从不变化。它定义了 Agent 的基本身份和行为准则:“你是一个 AI 编程助手,名叫 Claude。你的目标是帮助用户完成编程任务。”
这一层还包含通用行为约束:
- 用用户的语言回复
- 不确定时提问,不要猜测
- 解释你的推理过程
- 优先理解意图,再动手
第二层:工具定义(Tool Definitions)
上一篇文章我们聊过工具系统。工具的定义(名字、描述、参数 schema)就是注入到这一层的。这是 system prompt 里占比最大的部分之一——30 多个工具,每个都有结构化描述。
这一层是半动态的:核心工具始终存在,延迟工具按需加载。
第三层:项目上下文(Project Context)
这一层是 Claude Code 最有特色的设计之一。它会扫描项目目录,加载 .claude/CLAUDE.md 文件(类似 .editorconfig 的层级继承机制),把项目的特定约定注入到 prompt 里。
CLAUDE.md 可能包含:
- 项目的技术栈和架构说明
- 代码风格约定
- 测试策略
- 部署流程
- 已知问题和注意事项
这意味着同一个 Claude Code,在不同的项目里"表现不同"——不是模型变了,是注入的上下文变了。它在一个 React 项目里知道用 JSX,在一个 Rust 项目里知道用 Cargo。
第四层:会话状态(Session State)
最外层,最易变。包含当前会话的动态信息:
- 之前加载的记忆(Memory)
- 已激活的 Skill
- 当前权限模式
- 活跃的子代理
这一层在每次循环迭代时都可能变化。
四层叠加,就像一个洋葱。核心身份是内核,越外层越贴近当前任务的具体情境。
占位符与模板引擎
如果你看过 Claude Code 的 prompt.ts 源码,你会发现 system prompt 不是一个字符串,而是一个模板。它包含大量占位符,在运行时被替换为实际内容:
{git_context}
{claude_md_content}
{memory_content}
{skill_content}
{tool_definitions}
{subagent_definitions}
{deferred_tool_definitions}每个占位符对应一个数据源。运行时,prompt 构建器会遍历这些占位符,从对应的模块获取内容,替换进去。如果某个数据源为空(比如项目没有 CLAUDE.md),占位符被静默移除,不会在 prompt 里留下空壳。
这种模板化设计带来了两个好处:
可测试性。 每一层都可以独立测试——你可以验证"CLAUDE.md 加载是否正确"而不需要跑完整的 Agent 循环。
可组合性。 不同的项目、不同的用户偏好、不同的权限模式,组合出不同的 prompt。同一个代码库,适配无穷多的使用场景。
CLAUDE.md:项目宪法
CLAUDE.md 是 Claude Code 生态里最被低估的设计之一。
它的理念很简单:每个项目都应该有一份"宪法",告诉进入这个项目的 AI 代理该怎么干活。
这听起来像 README,但本质不同。README 是写给人看的——它介绍项目是什么、怎么安装、怎么用。CLAUDE.md 是写给 AI 看的——它告诉 AI 这个项目的约定、约束、偏好。
一个典型的 CLAUDE.md 可能长这样:
# 项目约定
- 使用 TypeScript,不写 JavaScript
- 组件用函数式 + Hooks,不用 Class
- 测试用 Vitest,每个新文件必须有对应测试
- 错误处理统一用 Result 模式,不用 try-catch
- Git 提交信息用 Conventional Commits 格式这段内容对人类开发者来说是"提醒",对 AI Agent 来说是硬约束。它被注入 system prompt,等同于模型的行为准则。
CLAUDE.md 还有层级继承机制——根目录的 CLAUDE.md 对所有子目录生效,子目录可以覆盖或追加。这和 .gitignore、.editorconfig 的逻辑一样,但对 AI Agent 的意义更大:它是项目知识的形式化载体。
这个设计的深层含义是:项目的规范不应该只存在于人的脑子里,它应该被写成机器可读的格式。 这不是新概念(配置文件早就存在了),但 CLAUDE.md 是第一个专门为 AI Agent 设计的"项目宪法"。
行为约束:怎么让 AI 不瞎搞
System prompt 里有一大块内容专门用来约束模型行为。这不是"建议",是"规则"。包括但不限于:
- 先理解再行动。 不要接到需求就立刻改代码。先读相关文件,理解现有架构,再动手。
- 最小变更原则。 只改需要改的部分,不要重构不相关的代码。
- 解释你的决定。 每次修改都要说明为什么这么改。
- 失败时回退。 如果操作导致错误,尝试恢复到操作前的状态。
- 不要编造文件内容。 如果没读过某个文件,不要假装知道它的内容。
这些约束的存在,说明了一个事实:模型默认行为不理想。 不加约束的模型倾向于"过度热情"——它太想帮忙了,以至于没搞清楚状况就动手,改了一堆不该改的东西。
System prompt 里的行为约束就像给一辆跑车装上 ABS 和 ESP。引擎很强,但如果没有刹车和稳定控制系统,开得越快死得越快。
延迟注入:不是所有信息都需要一开始就有
和工具系统的渐进式披露类似,system prompt 也采用了延迟注入策略。不是所有信息都在第一次调用时就塞进去,而是根据对话进展逐步注入。
比如 Skill 内容——用户有 10 个 Skill(写作规范、架构设计方法、测试策略……),但当前任务只需要"写作规范"。那其他 9 个 Skill 的内容就不注入,等模型明确需要时再加载。
再比如记忆——用户的历史记忆可能有几十条,但当前任务只相关其中 3 条。system prompt 只注入这 3 条,而不是全量注入。
延迟注入的核心原则是:信息越相关,越早注入;信息越不相关,越晚注入(或永不注入)。 这本质上是一个信息检索问题——在海量可用信息中找到最相关的子集。
System Prompt 的本质
写到这里,我们可以回答一个根本问题:System Prompt 到底是什么?
它不是"人设"。它不是"提示"。它是一个运行时配置——和程序的配置文件一样,它定义了 Agent 在当前环境下的行为边界、可用资源、操作规则。
只不过这个配置文件的格式是自然语言,解析器是大语言模型。
这也是为什么 System Prompt Engineering 越来越像软件工程——你需要模块化、模板化、可测试、可维护。你不只是"写一段话",你是在"构建一个框架"。
延伸阅读:BYF 的 PromptPlan 与 Ephemeral Injection
BYF 在 system prompt 工程上做了比 Claude Code 更极致的结构化处理。它有一个专门的 PromptPlan 模块,将 system prompt 拆分为有序的具名块(PromptBlock[]),每块附带 CacheScope 标注——global(跨会话稳定)、project(项目内稳定)、session(每会话变化)、none(不缓存)。这四块架构确保了:
- **全局块(Block 0)**只包含纯 Agent 规则——身份、原则、安全——不包含任何 per-session 变量,让 OpenAI 的
prompt_cache_key真正跨会话稳定。 - 环境块单独成块,
BYF_OS、BYF_SHELL、BYF_WORK_DIR等变量不污染全局缓存。 - 工具按稳定性排序,内置工具在前、MCP 工具在后,确保缓存边界不会随外部服务连接状态而坍缩。
BYF 还引入了临时注入(Ephemeral Injection)——一种更精细的上下文管理策略。时间戳和权限模式状态等每步变化的动态内容,不再持久写入对话历史(_history),而是通过 getEphemeral() 在每轮请求时重新渲染,追加在历史末尾的 before_user 位置。这样做的效果是:动态内容对缓存前缀零影响——整个 system prompt + 对话历史可以被缓存,只有末尾几十 token 的"新鲜"内容逐轮变化。
这个设计和 Claude Code 的分层注入思路一致,但 BYF 的"四块架构 + ephemeral 注入"在缓存优化上走得更远,是从"让 prompt 更长但有结构"到"让 prompt 的哪些部分能被缓存"的精细化管理。
下一篇
System prompt 定义了 Agent 的行为规则,但规则需要执行机制。上一篇文章聊了工具系统,这篇文章聊了行为约束,下一篇文章我们要看这两者的交汇点:权限与安全系统——Agent 什么时候可以直接行动、什么时候必须问用户、什么时候应该被禁止。5 级权限模型背后的设计逻辑,本质上是一个信任校准问题。
本系列基于 Claude Code 官方源码项目进行架构分析,聚焦设计思想而非代码实现。