写在前面

上一篇文章中,我们解析了 Claude Code 的架构、Tool、Skill,感兴趣可以翻一下。这篇文章我们就来讲讲CC的 顶级 Prompt 的组成。下一篇文章我们再来讲讲 memory 记忆模块。

Prompt 提示词

做过 Agent 的同学都知道,调 Prompt 是一个很痛苦的过程,不过我们现在可以看看顶级Agent的提示词是怎么做的。

CC 的 Prompt 提示词主要分成以下几个部分:
在这里插入图片描述

  1. Core System Prompt: 明确角色、任务边界、输出风格、风险动作原则、工具总原则。
  2. Tool Prompts: 每个工具的用途、输入约束、什么时候用、什么时候不用、与其他工具的边界。
  3. Skill Prompts: 专项知识包、明确触发条件、限定工具集、可按需展开。
  4. Agent Prompts: coordinator、worker、verifier、planner。
  5. Context Management Prompts: 压缩、会话总结、记忆提取、恢复。
  6. 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 的覆盖关系清晰、一致、可维护。
在这里插入图片描述

  1. Override SystemPrompt:P0最高优先级,如果设置了 override prompt,直接替换掉其他所有 prompt,其他什么default/custom/agent/coordinator 都不管了,这就是硬覆盖
  2. Coordinator Prompt:如果当前开了 coordinator mode,就要用 coordinator 专用的 system prompt 来代替默认 prompt,当前主线程不再是普通 agent,而是一个调度者
  3. Agent Prompt:如果设置了 mainThreadAgentDefinition,主线程本身就变成某个 agent,那通常用这个 agent 自己的 system prompt。 一般情况下,agent prompt 替换 default prompt,但在 proactive mode 下,agent prompt 会追加到 default prompt 后面,不替换 default。
  4. Custom System Prompt:如果用户传了 --system-prompt 并且前面都没有的情况下就用这个Custom System Prompt
  5. 最后才是真正的系统默认的 Default System Prompt

在这里插入图片描述

Tool Prompts

cc 里面skill和sub agent都是以tool的形式调用的,比如 ToolSkill、ToolAgent 之类的。

cc 中的每个tool基本都有自己的 prompt/description 来规定自身的 说明方式和工具间的边界 ,这类 prompt 的特点是 行为协议,可以使用什么,不要使用什么。典型结构就是:

  1. 这个工具是什么?
  2. 什么时候该用/什么时候不该用?
  3. 参数/调用约束是什么?

举个例子,比如 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标签来区别这个文档内容来自哪里文档,后续就不会重复找相同的文件。

<doc path="typescript/claude-api/README.md">
...文档内容...
</doc>

<doc path="shared/tool-use-concepts.md">
...文档内容...
</doc>

整个skill的prompt排版如下:

在这里插入图片描述

伪 markdown 如下:

***
name: Claude API
description: 这个技能用于帮助你使用 Claude API、Anthropic SDK 或 Agent SDK 构建应用,当你处理以下问题时,应优先使用这份技能...
allowed-tools:
- Read
- WebFetch
- ...

***

# Claude API / Anthropic SDK 专项技能

## Reference Documentation
...根据 go 定制的 reading guide...
---
## Included Documentation
<doc path="go/claude-api/README.md">...</doc>
<doc path="shared/tool-use-concepts.md">...</doc>
...更多相关 docs...
## When to Use WebFetch
...
## Common Pitfalls
避免错误使用模型名、错误流式写法、错误 tool use 方式、缓存误解等...
## User Request
Use Go SDK to stream chat responses

Agent 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主要有这几个部分组成:
在这里插入图片描述

  1. 定义角色:一开始先告诉模型,你有一个持久的、文件化的 memory 系统,路径在哪,目录已经存在,可以直接写。
  2. 定位意义:为了逐步积累对用户、协作方式、项目背景的理解,让未来会话能延续上下文。
  3. 明确 remember / forget 是一等动作:用户显式说“记住”就立即存,用户说“忘记”就立马删除。
  4. memory类别:user、feedback、project、reference,每一类都定义了desc、when to save、how to use、examples等等…

比如 user 是记用户角色、目标、知识水平、偏好的,用途是让后续解释和协作更贴合用户。例如:用户是资深 Go 开发,但不熟 React,那以后解释前端问题时就要借后端类比。

在这里插入图片描述

  1. 如何存储记忆:每条 memory 都会按照以下格式写到自己的 markdown 文件里
export const 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: <auto memory dir>
- team memory: <team memory dir>   (如果启用 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 memory
memory 里的内容可能过时。
如果 memory 提到了文件、函数、flag,先验证它现在是否还存在。
如果用户问的是当前状态,优先读代码和当前仓库状态,而不是盲信 memory。

## Memory and other forms of persistence
不要把 plan、task、当前会话临时状态存进 memory。
plan 用来记录方案,tasks 用来跟踪当前工作,memory 留给未来会话仍有价值的东西。

## Searching past context
如果要找历史上下文:
1. 先搜 memory topic files
2. 实在不够再搜 transcript jsonl

受篇幅有限,记忆的压缩、提取、召回我们下一篇再详细展开说说。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐