写在前面
上一篇文章中,我们解析了 Claude Code 的架构、Tool、Skill,感兴趣可以翻一下。这篇文章我们就来讲讲CC的 顶级 Prompt 的组成。下一篇文章我们再来讲讲 memory 记忆模块。
Prompt 提示词
做过 Agent 的同学都知道,调 Prompt 是一个很痛苦的过程,不过我们现在可以看看顶级Agent的提示词是怎么做的。
CC 的 Prompt 提示词主要分成以下几个部分:
Core System Prompt: 明确角色、任务边界、输出风格、风险动作原则、工具总原则。 Tool Prompts: 每个工具的用途、输入约束、什么时候用、什么时候不用、与其他工具的边界。 Skill Prompts: 专项知识包、明确触发条件、限定工具集、可按需展开。 Agent Prompts: coordinator、worker、verifier、planner。 Context Management Prompts: 压缩、会话总结、记忆提取、恢复。 Memory Prompts: 存储内容、存储方式等等。
Core System Prompt
整个系统提示词是由静态规则和动态的 dynamicSections 组成。静态规则会做缓存,动态规则会做更新,并且静态和动态规则之间会有一个boundary做划分。
其实我们可以从cc的代码中看到有很多的明切的边界划分,不仅是在 system prompt 这里,还有上一篇文章的 tool、skill 的划分,都是非常明确的界限。
静态规则 比如:
if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {return [`You are Claude Code, Anthropic's official CLI for Claude.\n\nCWD: ${getCwd()}\nDate: ${getSessionStartDate()}`, ] }dynamicSections 比如:
const dynamicSections = [ systemPromptSection('session_guidance', () => getSessionSpecificGuidanceSection(enabledTools, skillToolCommands)), systemPromptSection('memory', () => loadMemoryPrompt()), systemPromptSection('language', () => getLanguageSection(settings.language)), systemPromptSection('output_style', () => getOutputStyleSection(outputStyleConfig)), DANGEROUS_uncachedSystemPromptSection('mcp_instructions',() =>isMcpInstructionsDeltaEnabled()? null: getMcpInstructionsSection(mcpClients),'MCP servers connect/disconnect between turns'), systemPromptSection('summarize_tool_results',() => SUMMARIZE_TOOL_RESULTS_SECTION) ...]⚠️ 注意在 system prompt 拼接的时候,还有一个 优先级策略树 buildEffectiveSystemPrompt,保证在多模式、多角色、多来源 prompt 共存时, system prompt 的覆盖关系清晰、一致、可维护。
Override SystemPrompt:P0最高优先级,如果设置了 override prompt,直接替换掉其他所有 prompt,其他什么default/custom/agent/coordinator 都不管了,这就是硬覆盖。Coordinator Prompt:如果当前开了 coordinator mode,就要用 coordinator 专用的 system prompt 来代替默认 prompt,当前主线程不再是普通 agent,而是一个调度者。Agent Prompt:如果设置了 mainThreadAgentDefinition,主线程本身就变成某个 agent,那通常用这个 agent 自己的 system prompt。 一般情况下,agent prompt 替换 default prompt,但在 proactive mode 下,agent prompt 会追加到 default prompt 后面,不替换 default。Custom System Prompt:如果用户传了 --system-prompt 并且前面都没有的情况下就用这个Custom System Prompt最后才是真正的系统默认的 Default System Prompt

Tool Prompts
cc 里面skill和sub agent都是以tool的形式调用的,比如 ToolSkill、ToolAgent 之类的。
cc 中的每个tool基本都有自己的 prompt/description 来规定自身的 说明方式和工具间的边界 ,这类 prompt 的特点是 行为协议,可以使用什么,不要使用什么。典型结构就是:
这个工具是什么? 什么时候该用/什么时候不该用? 参数/调用约束是什么?
举个例子,比如 GrepTool:
⚠️ 注意!cc里面会把一些规则以自然语言的形式放在Prompt里面,而不是以代码的形式对大模型的输出进行做规则定义 比如这里面的 to find interface in Go Code ,自身的代码并没有做过多的规则补丁,而是充分相信大模型的处理。
我们再看一个 BashTool 的例子,这个 Tool 的 Desc 已经复杂的不是简单声明了,更像一个高风险工具专用操作规程SOP。这里面定义了git的提交 PR 的详细流程,什么事情不能做,用skill替代部分git流程等等...
让我感觉更像一个初版的 Skill,有点怀疑是不是因为这个 BashTool 的 Desc 太多了,而有了后来的 Skill。

Skill Prompts
如果我们都用mcp的话,就会导致上下文窗口存在大量的tool定义、描述、参数,但一般模型只会选择部分tool执行,那么就会有token的浪费,所以就出现了渐进式加载的skill。skill 一种 Command(type='prompt') 形式的可展开能力包,支持渐进式加载,其实就是一段标准的SOP。
核心机制是: 先把 skill 作为 prompt 资产注册起来,再由 SkillTool 在运行时把它展开成新的上下文消息 ,而不是像普通 tool 那样直接执行外部动作。 我们用一个cc里面的一个skill来举个例子,看看cc里面是怎么写skill的,比如 claude-api 的 skill
一个skill里面会包含这些核心能力:name/description、allowedTools、model、hooks、paths 等等...
name:这个skill的名字。 description:这个skill的使用场景,什么时候触发,什么时候不触发。 allowedTools:可以允许使用的工具集合。 buildPrompt:如何构建当前这个skill的 prompt。
prompt生成规则:先找到 ## Reading Guide,然后把 SKILL_PROMPT 分成两段,前半段 basePrompt 会保留,中间的 reading guide 不直接用原始版本,而是用运行时生成版替换掉 ,我们来看看这个 reading guide是什么:

简单来说就是一个索引文件,遇到不同任务时该读哪些 docs,文档入口在哪:
单轮文本分类 / 摘要 / 信息抽取 / 问答 → 看 {lang}/claude-api/README.md聊天 UI 或实时流式响应展示 → 看 {lang}/claude-api/README.md+{lang}/claude-api/streaming.md长对话(可能超过上下文窗口) → 看 {lang}/claude-api/README.md中的 Compaction 部分等等...
skill 不会把所有语言文档都发给模型,只发当前项目最可能相关的那一套,这也是一种非常重要的 token 优化策略,这里的lang 是根据detectLanguage这个函数来判断的,比如有以下的一些策略:
pyproject.toml / requirements.txt → Python package.json / tsconfig.json → TypeScript go.mod → Go pom.xml → Java
如果没有检测出来是什么语言,会直接咨询用户当前的编程语言, 并且 prompt 拼接内容的时候,还会用doc标签来区别这个文档内容来自哪里文档,后续就不会重复找相同的文件。
<docpath="typescript/claude-api/README.md">...文档内容...</doc><docpath="shared/tool-use-concepts.md">...文档内容...</doc>整个skill的prompt排版如下:

伪 markdown 如下(实际场景中要么纯英文,要么纯中文):
***name: Claude APIdescription: 这个技能用于帮助你使用 Claude API、Anthropic SDK 或 Agent SDK 构建应用,当你处理以下问题时,应优先使用这份技能...allowed-tools:- Read- WebFetch- ...***# Claude API / Anthropic SDK 专项技能## Reference Documentation...根据 go 定制的 reading guide...---## Included Documentation<docpath="go/claude-api/README.md">...</doc><docpath="shared/tool-use-concepts.md">...</doc>...更多相关 docs...## When to Use WebFetch...## Common Pitfalls避免错误使用模型名、错误流式写法、错误 tool use 方式、缓存误解等...## User RequestUse Go SDK to stream chat responsesAgent Prompts
这里有两种 Agent Prompt,一种是给主线程看的,本质是告诉主线程如何使用 AgentTool,这个prompt由以下这几个部分组成:
Shared core: 什么是 AgentTool、available agents 列表、subagent_type/fork 的基本语义... When NOT to use: 读一个文件别开 agent、搜一个类定义别开 agent等等... Usage notes: description 要怎么写、前台/后台 agent 的区别... Writing the prompt: 如果是 fresh agent,要把背景讲完整、如果是 fork,要写 directive、不要重复背景.. When to fork: 仅在 fork 功能开启时出现、强调 fork 继承上下文、不要偷看 output_file、不要猜结果等等... Examples: 给主模型示范什么时候该开 agent、coordinator/fork 模式和普通模式示例不同等等...

另一种Agent Prompt是给具体的agent做 system prompt 用的,比如这个agent是什么、充当什么角色、边界在哪里、输出是什么等等... 这类 prompt 有着强角色边界,强流程编排,特别像人类团队里的 TL/PM 操作手册, 抽象成可复用的模块大概是以下这个样子:
你是一个 xxx 角色.## 你的工作职责是- 你负责什么- 你的核心价值是什么## 强制边界- 你绝对不能做什么- 哪些行为会失败或被拒绝## 你可以获取的信息- 你会拿到什么输入- 哪些上下文可以依赖## 执行过程1. 先做什么2. 再做什么3. 什么时候停止4. 什么时候升级/转交## 错误处理- 你最常见的错误行为是什么- 出现时应该如何纠正## 工具使用指南- 应该优先怎么用工具- 哪些工具不能碰- 哪些信号要检查而不是假设## 输出的结果是什么- 必须怎么汇报结果- 必须包含哪些字段- 是否需要 verdict / critical files / summary我们的prompt是给大模型看的,所以尽量是模型友好型的语句格式,尽量不要弄 json、key、value 之类的编码类的语言,用有逻辑的自然语言表达描述。
Memory Prompts
Memory的Prompt主要有这几个部分组成:
定义角色:一开始先告诉模型,你有一个持久的、文件化的 memory 系统,路径在哪,目录已经存在,可以直接写。 定位意义:为了逐步积累对用户、协作方式、项目背景的理解,让未来会话能延续上下文。 明确 remember / forget 是 一等动作:用户显式说“记住”就立即存,用户说“忘记”就立马删除。memory类别:user、feedback、project、reference,每一类都定义了desc、when to save、how to use、examples等等...
比如 user 是记用户角色、目标、知识水平、偏好的,用途是让后续解释和协作更贴合用户。例如:用户是资深 Go 开发,但不熟 React,那以后解释前端问题时就要借后端类比。

如何存储记忆:每条 memory 都会按照以下格式写到自己的 markdown 文件里
exportconst MEMORY_FRONTMATTER_EXAMPLE: readonly string[] = ['```markdown','---','name: {{memory name}}','description: {{one-line description — used to decide relevance in future conversations, so be specific}}',`type: {{${MEMORY_TYPES.join(', ')}}}`,'---','','{{memory content — for feedback/project types, structure as: rule/fact, then **Why:** and **How to apply:** lines}}','```',]大体的形态如下:
# Memory你有一个持久化的、基于文件的 memory 系统,位于:- private memory: <automemorydir>- team memory: <teammemorydir> (如果启用 team 模式)你应该逐步建立这套 memory 系统,让未来的会话能够知道:- 用户是谁- 用户喜欢如何协作- 哪些行为应该避免- 当前工作背后的上下文如果用户明确要求你记住某件事,立即保存。如果用户要求你忘记某件事,找到对应条目并删除。## Memory scope- private:只在你和当前用户之间共享- team:项目内团队共享## Types of memory### user记录用户的角色、目标、职责、知识水平、偏好。适合在你需要根据用户背景调整解释和协作方式时使用。### feedback记录用户对你工作方式的反馈,包括:- 不要怎么做- 哪种非显然做法被验证是好的写法:- 先写规则- 再写 Why:- 再写 How to apply:### project记录项目中的背景事实、决策、动机、截止时间、事故背景。前提:这些信息不能从代码、git、CLAUDE.md 直接推导出来。写法:- 先写事实/决策- 再写 Why:- 再写 How to apply:### reference记录外部系统的入口:- 哪个看板- 哪个 dashboard- 哪个 Slack 频道- 哪个 Linear project## What NOT to save不要保存:- 代码结构、架构、文件路径- git 历史和改动记录- 修 bug 的具体 recipe- 已写在 CLAUDE.md 的内容- 当前会话中的临时任务状态## How to save memories保存 memory 分两步:Step 1把每条 memory 写到单独文件中,带 frontmatter:---name: ...description: ...type: user|feedback|project|reference---Step 2把这个文件的入口加到 MEMORY.md:- [Title](file.md) — one-line hook规则:- MEMORY.md 只是索引,不要把正文写进去- 按主题组织,不按时间组织- 优先更新旧 memory,不要重复写## When to access memories- memory 看起来相关时去读- 用户明确要求 recall/check/remember 时必须读- 用户说 ignore memory 时,就当 memory 为空## Before recommending from memorymemory 里的内容可能过时。如果 memory 提到了文件、函数、flag,先验证它现在是否还存在。如果用户问的是当前状态,优先读代码和当前仓库状态,而不是盲信 memory。## Memory and other forms of persistence不要把 plan、task、当前会话临时状态存进 memory。plan 用来记录方案,tasks 用来跟踪当前工作,memory 留给未来会话仍有价值的东西。## Searching past context如果要找历史上下文:1. 先搜 memory topic files2. 实在不够再搜 transcript jsonl受篇幅有限,记忆的压缩、提取、召回我们下一篇再详细展开说说。
夜雨聆风