Claude Code 系统提示词:智能体开发者的工程启示
上个月,我看到一个内部编程智能体删错了生成文件,“修复”了由此造成的测试失败,然后信心满满地报告成功。根本问题不在于模型质量,而在于边界设置不当:智能体被允许编辑的范围过大,关于生成产物的规则含糊不清,而且执行破坏性操作前无需确认。
这就是为什么最近围绕 Claude 和 Claude Code 系统提示词的公开讨论很有价值,即便你不应将泄露或收集到的提示词奉为圭臬。提示词转储通常并非官方资料,可能不完整、已过时,或提取自已经不存在的产品状态。Claude Code 本身演进迅速,而当前的 Claude 产品阵容——Opus 4.8、Sonnet 4.6、Haiku 4.5,以及具有 1M 上下文的 Fable 5——为智能体构建者带来的取舍与早期版本大不相同。
但值得关注的经验并不是“复制这段提示词”。真正有用的经验在于架构:成熟的编程智能体会将操作边界、指令优先级、确认规则、文件访问规范和失败时的行为编码到模型周围的系统中。
本文探讨的就是这些经验。
不复制提示词,也能借鉴 Claude Code 系统提示词的经验
“Claude Code 系统提示词”已经成为“一个成熟的编程智能体如何告诉模型该怎样行动?”的简略说法。如果这个问题让你陷入提示词考古,那方向就错了;如果它引导你改进智能体设计,那方向就是对的。
在实践中,系统提示词只是其中一层。生产级智能体通常至少有五个相互协作的控制层面:
| 层级 | 控制内容 | 示例 |
|---|---|---|
| 系统指令 | 稳定的行为约定 | “优先采用最小改动;除非被要求,否则不要提交。” |
| 开发者策略 | 产品特定规则 | “使用 apply_patch;未经批准,绝不运行破坏性命令。” |
| 工具模式 | 模型实际能够执行的操作 | read_file, edit_file, run_shell, update_plan |
| 运行时权限 | 进程被允许访问的内容 | 沙箱、工作目录、网络策略 |
| 用户指令 | 任务特定目标 | “修复失败的身份验证测试。” |
我在许多自建智能体中看到的错误,是把这些层面合并成一大段提示词。这在演示中行得通,但在复杂情境下就会失效。
更好的方法是用提示词说明意图,用运行时强制约束能力。
例如,不要仅仅在提示词中写:
Do not delete files unless the user asks.
将删除操作设为需要确认的独立工具:
{
"name": "delete_file",
"description": "Delete a file from the workspace.",
"input_schema": {
"type": "object",
"properties": {
"path": { "type": "string" },
"reason": { "type": "string" },
"user_confirmed": { "type": "boolean" }
},
"required": ["path", "reason", "user_confirmed"]
}
}
然后在模型之外强制执行这一要求:
def delete_file(path: str, reason: str, user_confirmed: bool):
if not user_confirmed:
raise PermissionError("delete_file requires explicit user confirmation")
if not path.startswith(WORKSPACE_ROOT):
raise PermissionError("cannot delete files outside workspace")
os.remove(path)
提示词告诉智能体如何思考,工具边界决定哪些操作能够发生。
指令层级是一种工程接口
Claude 模型善于遵循分层指令,但你仍然需要仔细设计这些层级。如果你的编程智能体具有系统、开发者、项目和用户指令,那么每一层都应有明确的职责。
一种实用的层级结构如下:
- 系统指令: 身份、安全立场、高层操作原则。
- 开发者指令: 产品机制、工具使用规则、回复风格、权限模型。
- 项目指令: 代码仓库特定约定、测试命令、架构说明。
- 用户指令: 当前任务。
Claude Code 风格的工作流通过这类文件体现这一点,例如 CLAUDE.md,再结合命令行配置和会话上下文。对于智能体构建者,更重要的经验是:项目本地指令应有明确的适用范围,并且只在适当的情况下覆盖其他指令。
一个简单的项目指令文件可能如下所示:
# CLAUDE.md
## Build and Test
- Use `pnpm test -- --runInBand` for focused test runs.
- Do not run `pnpm deploy` unless explicitly requested.
## Code Style
- Prefer small functions over large service classes.
- Do not add new dependencies without explaining why.
## Repo Notes
- API routes live in `apps/api/src/routes`.
- Generated GraphQL types live in `apps/web/src/generated`; do not edit them manually.
一个常见陷阱是:智能体往往将所有指令视为具有同等的全局优先级。如果用户说“忽略项目规则,直接编辑生成文件”,你的智能体需要知道这是否被允许。在大多数配置中,用户指令不应悄然覆盖优先级更高的项目或开发者约束。
你可以对此进行显式建模:
PRIORITY = {
"system": 4,
"developer": 3,
"project": 2,
"user": 1,
}
def resolve_conflict(existing_rule, new_rule):
if PRIORITY[new_rule.source] > PRIORITY[existing_rule.source]:
return new_rule
return existing_rule
在实际的智能体中,模型还应向用户解释冲突:
I can update the source schema and regenerate the types, but I should not edit
`src/generated` directly because the project instructions mark it as generated.
仅这一句话就能避免大量意想不到的损害。
工具边界比提示词中的礼貌要求更有效
人们查看编程智能体的提示词时,往往关注措辞:“简洁一些”“像资深工程师那样行动”“不要迎合用户”。这些要求有用,但并不是核心安全机制。
核心机制是工具边界。
一个成熟的编程智能体应区分以下操作:
- 读取文件
- 搜索文件
- 编辑文件
- 运行命令
- 安装软件包
- 访问网络资源
- 删除或移动文件
- 创建提交或分支
- 发起拉取请求
- 更改密钥或部署配置
这些操作在责任与风险上并不等同。
下面是我在设计智能体运行时时使用的实用权限矩阵:
| 操作 | 默认策略 | 原因 |
|---|---|---|
| 读取工作区文件 | 允许 | 收集上下文所必需 |
使用以下工具搜索 rg | 允许 | 风险低且非常有用 |
| 编辑已跟踪的源文件 | 允许,但须展示差异 | 核心编程工作流 |
| 运行有针对性的测试 | 通常允许 | 验证更改 |
| 安装依赖 | 询问 | 会更改环境和锁定文件 |
| 删除文件 | 询问 | 容易造成不可逆的损失 |
| 访问网络 | 询问或拒绝 | 可能泄露数据或引入非确定性 |
| 编辑密钥 | 默认拒绝 | 潜在影响范围大 |
| 提交更改 | 询问 | 代码仓库历史归用户所有 |
| 部署 | 除非明确要求,否则拒绝 | 影响生产环境 |
这也是模型选择发挥作用的地方。对于大多数编程智能体循环,我倾向于使用 Sonnet 级别的模型,因为它们在延迟、成本和推理能力之间取得了良好平衡。当智能体需要更深入的架构综合分析或复杂调试时,我会选择 Opus 4.8。Haiku 4.5 适合快速分类和轻量级代码仓库扫描。Fable 5 的 1M 上下文在理解大型代码库方面值得关注,但长上下文并不能替代良好的检索、限定范围的读取和规范的工具使用。
一百万个 token 的上下文中仍然可能包含错误的文件。
用户确认应当具体明确,而非流于形式
糟糕的确认交互是这样的:
May I proceed?
继续做什么?编辑一个文件?运行迁移?删除一个目录?
好的确认应当具体明确:
I need confirmation before running:
`rm -rf apps/web/.next`
Reason: remove a stale Next.js build cache that may be causing the local
runtime error. This deletes only generated build output and does not touch
source files.
对于类似 Claude Code 的智能体,这一点很重要,因为模型可能正确推断出下一步操作,但仍然需要获得人工授权。如果每次提示都只是要求笼统的批准,用户就会产生审批疲劳,直接点击通过。如果只有高风险操作才需要精确确认,用户就能保持关注。
我建议将确认表示为结构化状态,而不是文字描述:
{
"action": "run_shell",
"command": "rm -rf apps/web/.next",
"risk": "destructive_generated_artifact",
"requires_confirmation": true,
"confirmation_prompt": "Delete generated Next.js cache at apps/web/.next?"
}
这样,你的 shell 执行器就可以强制执行确认要求:
def run_shell(command, confirmed=False):
risk = classify_command(command)
if risk.requires_confirmation and not confirmed:
return {
"status": "needs_confirmation",
"message": risk.prompt
}
return subprocess.run(command, shell=True, cwd=WORKSPACE_ROOT)
在实践中,命令分类永远无法做到完美。这没关系。采用保守的默认策略。如果分类器无法确定某条命令是否具有破坏性,就询问用户。
需要谨慎处理的命令
这些命令值得制定明确的策略:
rm -rf path
git reset --hard
git clean -fd
git push --force
npm install some-package
pip install -r requirements.txt
docker compose down -v
kubectl delete ...
terraform apply
有些命令在适当的情境下无害,有些在不当的情境下则可能造成灾难性后果。智能体应当知道,二者的区别取决于上下文,并在影响范围不明确时询问用户。
文件访问就是上下文工程
编程智能体设计中最被低估的部分是文件访问。提示词可以要求“先检查相关文件”,但运行时应当让这种行为易于执行。
至少要为模型提供用于以下操作的工具:
- 快速查找文件名
- 文本搜索
- 读取限定范围的文件内容
- 查看 git 差异
- 以原子方式应用补丁
一个最小工具集可能如下所示:
[
{
"name": "search_files",
"input_schema": {
"type": "object",
"properties": {
"query": { "type": "string" },
"glob": { "type": "string" }
},
"required": ["query"]
}
},
{
"name": "read_file",
"input_schema": {
"type": "object",
"properties": {
"path": { "type": "string" },
"start_line": { "type": "integer" },
"limit": { "type": "integer" }
},
"required": ["path"]
}
},
{
"name": "apply_patch",
"input_schema": {
"type": "object",
"properties": {
"patch": { "type": "string" }
},
"required": ["patch"]
}
}
]
限定读取范围很重要。如果智能体读取单体仓库中的每个文件,就会浪费上下文,而且通常表现更差。如果读取太少,就只能靠猜测。理想做法是迭代获取上下文:
rg "createSession|verifySession|SessionStore" apps packages
sed -n '1,220p' apps/api/src/auth/session.ts
sed -n '1,180p' apps/api/src/routes/login.ts
git diff -- apps/api/src/auth/session.ts
Claude Code 用户会熟悉这种节奏:搜索、检查、编辑、验证。智能体开发者应当将这一循环纳入默认行为。
一个常见陷阱是:文件工具需要保留换行符、权限,并遵循预期的编码方式。我见过智能体“成功”重写 shell 脚本,却移除了可执行权限,或者以产生大量无关差异的方式规范化文件。你的编辑层应当朴实、保守。
失败模式:智能体出错时究竟会发生什么
大多数编程智能体不会以显而易见的方式失败。它们的失败往往看起来合情合理。
以下是我最常见到的失败模式。
1. 智能体只解决表面症状
测试失败,因为 API 返回了 401。智能体将测试修改为预期返回 401。构建通过了,产品却坏了。
缓解措施:
- 要求智能体解释因果链。
- 在大范围重写之前,要求进行有针对性的测试。
- 优先修复源代码,而不是更新快照,除非用户要求改变行为。
2. 智能体过度编辑
用户要求修复一个小问题。智能体却重新格式化了五个文件、重命名了变量,还修改了公共 API。
缓解措施:
Make the minimal change that fixes the issue. Do not perform opportunistic
refactors unless they are necessary for correctness.
同时也要强制审查差异。在完成之前,展示修改的文件和行数:
git diff --stat
git diff -- apps/api/src/auth/session.ts
3. 智能体信任过时的上下文
长时间运行的会话会积累过时的假设。用户在外部修改了文件,测试现在以不同的方式失败,而智能体仍然沿用旧的理解。
缓解措施:
- 如果文件可能已发生变化,在编辑前重新读取。
- 检查
git diff在应用补丁之前。 - 将工具输出视为比对话记忆更新的信息。
4. 智能体掩盖工具失败
一条 shell 命令失败了,但智能体却当作成功了一样继续执行。
缓解措施:工具结果应当结构化,并且难以忽略。
{
"tool": "run_shell",
"command": "pnpm test auth",
"exit_code": 1,
"stdout": "...",
"stderr": "Cannot find module '@repo/config'",
"status": "failed"
}
然后指示智能体:
If a tool call fails, do not claim success. Diagnose the failure or clearly
report the blocker.
5. 智能体将机密信息写入上下文
如果智能体可以读取 .env、崩溃转储或生产环境配置,它可能会意外将机密信息包含在摘要、日志或提示词中。
缓解措施:
- 默认阻止访问已知包含机密信息的文件。
- 对工具输出中类似令牌的值进行脱敏。
- 访问包含机密信息的路径时,必须获得用户明确批准。
路径策略示例:
DENY_PATTERNS = [
".env",
".env.*",
"**/secrets/**",
"**/*.pem",
"**/*.key",
]
def can_read(path):
return not any(fnmatch(path, pattern) for pattern in DENY_PATTERNS)
通过提示词要求模型不要泄露机密信息是好的做法。防止模型接触不必要的机密信息则更好。
设计 Claude API 智能体循环
如果你直接基于 Claude API 构建,最简单且实用的循环是:
- 发送系统指令和开发者指令。
- 提供用户任务。
- 让 Claude 请求调用工具。
- 在你的运行时中执行工具。
- 返回工具结果。
- 重复执行,直到模型生成最终答复。
简化后的请求结构如下所示:
{
"model": "claude-sonnet-4-6",
"max_tokens": 4096,
"system": "You are a coding agent. Make minimal, correct changes...",
"messages": [
{
"role": "user",
"content": "Fix the auth timeout bug and add a regression test."
}
],
"tools": [
{
"name": "read_file",
"description": "Read a bounded section of a workspace file.",
"input_schema": {
"type": "object",
"properties": {
"path": { "type": "string" },
"start_line": { "type": "integer" },
"limit": { "type": "integer" }
},
"required": ["path"]
}
}
]
}
工具调用响应在概念上如下所示:
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "I’ll inspect the auth timeout path first."
},
{
"type": "tool_use",
"name": "read_file",
"id": "toolu_123",
"input": {
"path": "apps/api/src/auth/session.ts",
"start_line": 1,
"limit": 220
}
}
]
}
随后,你的运行时返回结果:
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_123",
"content": "export function createSession(...) { ... }"
}
]
}
具体的 SDK 语法取决于客户端版本,但设计原则始终一致:Claude 提出操作,你的运行时执行或拒绝这些操作,模型在下一轮收到执行结果。
这种分离使系统保持易于理解。
值得研究的 Claude Code 命令和设置
即使你不直接使用 Claude Code,它的工作流也能提供有用的产品设计模式。
典型的项目初始化流程:
claude
/init
这会创建或更新项目指导信息,让后续会话了解仓库特有的约定。
实际会话通常如下所示:
claude "Find why the billing webhook test is flaky. Do not edit files yet."
claude "Patch the smallest fix and run the focused test."
claude "Show me the diff and summarize remaining risks."
我喜欢这种方式,因为它将调查、修改和验证分开。智能体开发者可以将这些环节对应为不同模式:
{
"mode": "investigate",
"can_edit": false,
"can_run_tests": true,
"requires_confirmation_for": ["network", "delete", "install"]
}
然后显式切换模式:
{
"mode": "implement",
"can_edit": true,
"can_run_tests": true,
"requires_confirmation_for": ["network", "delete", "install", "commit"]
}
关键不在于具体命令,而在于交互约定。用户应当知道智能体何时只是在读取,何时正在修改文件,以及何时即将执行会产生副作用的操作。
为什么提示词转储很脆弱
那个收集系统提示词和模型配置的 GitHub 仓库之所以广受欢迎,是因为工程师天然会好奇真实工具是如何接受指令的。我理解它的吸引力。阅读提示词可以揭示产品设计取向:开发者担心哪些风险、优化哪些工作流,以及如何与模型沟通。
但复制来的提示词很脆弱,原因有几个:
- 它们可能并非来自官方。 你可能不知道它们是如何提取的,也不知道是否完整。
- 它们可能已经过时。 智能体提示词会随着模型、工具和产品策略的变化而变化。
- 它们依赖隐藏的运行时行为。 如果没有工具实现,提及该工具的提示词就毫无用处。
- 它们包含特定产品的取舍。 Claude Code 的约束可能不符合你的智能体的威胁模型。
- 它们可能造成虚假的信心。 精心编写的提示词并不能强制执行权限控制。
将这些资料用作设计灵感,而不是依赖项。
更好的练习是问:这条指令在解决什么问题?然后在自己的技术栈中,通过提示词和运行时的共同支持来解决这个问题。
实践要点
- 将这场
Claude Code system prompt对话视为一次架构讨论,而不是复制粘贴的机会。 - 将稳定的行为放在系统或开发者指令中,将仓库约定放在项目文件中,将任务目标放在用户消息中。
- 通过工具和运行时权限强制落实高风险操作的边界,而不只是依靠文字说明。
- 对于具有破坏性、涉及网络、修改依赖项或影响生产环境的操作,要求具体明确的确认。
- 为智能体提供限定范围的文件访问、快速搜索、原子补丁应用,以及结构化的工具失败信息。
- 围绕实践中有效的循环进行优化:检查、规划、最小化编辑、验证、总结风险。
- 假定复制来的提示词可能已经过时或不完整;围绕明确的能力和清晰的失败处理机制,构建持久可靠的智能体行为。
一个 API 密钥即可使用 Claude Opus 5.5、Sonnet 5、Haiku 4.5 和 Fable 5.1,以及 GPT-6 个模型。按量付费,无需订阅。
获取 API 密钥 →