合集

Claude Agent SDK 实战

已更新 27 章
阅读进度 52%

第 13 章

上下文与技能:system prompt、CLAUDE.md、skills

你写了 CLAUDE.md,它为什么没读?

先说一个我排查了半天的事故。

同一份代码,在我笔记本上跑得好好的,部署到服务器就变了:Agent 开始猜测试命令,还把一个内部约定忘了。

我以为是模型差异,查了版本、温度、提示词,最后发现是 setting_sources 没写。

它的默认值是 None,意思是“全部加载”:用户目录、项目、本地设置都读。我笔记本的用户目录里有一份 CLAUDE.md,服务器上没有。Agent 在本地“知道”的规则,服务器上根本不存在。

同一份代码,两个行为。不是模型的问题,是上下文来源的问题。

这一章讲三件事:system_prompt 到底要不要写、CLAUDE.md 从哪来、Skill 什么时候该用。


13.1 system_prompt 默认是空的

很多人以为 SDK 会自带 Claude Code 的系统提示。不会。

system_prompt 不设,就是空。你拿到的是一个“没有角色、没有工作流、没有完成标准”的 Agent;它能不能干活,全看你给了什么。

三种写法:

python
# 1. 从零写
system_prompt="You are a coding agent. Read before editing; run tests before done."

# 2. 用 Claude Code 的预设,再追加
system_prompt={
    "type": "preset",
    "preset": "claude_code",
    "append": "This repo uses unittest, not pytest.",
}

# 3. 不写(空)
# system_prompt 不传

怎么选?

  • 从零写:你有明确的工作流、输出格式、领域规则,不想继承 Claude Code 的行为。
  • preset + append:你想要 Claude Code 那套“先读、后改、跑测试”的默认行为,只补项目约定。
  • 不写:只做很短的任务,或者你完全靠 CLAUDE.md/Skill 提供上下文。

但不管选哪种,CLAUDE.md 都和 system_prompt 无关。你写不写系统提示,都不影响 CLAUDE.md 加不加载;决定它的是 setting_sources。


13.2 CLAUDE.md 不在系统提示里

这是第二个容易误解的点。

CLAUDE.md 不是拼在 system prompt 里的。它由 CLI 加载,放在第一条用户消息里,每次请求都带全文。

所以:

  • 它每一轮都付钱,和系统提示一样;
  • 它能被缓存,只要前缀稳定;
  • 它不是“项目文档仓库”,几百字够了,长文档按需读。

taskflow 的 CLAUDE.md 只有三行:

markdown
# taskflow

- Fix `taskflow/`, not `tests/`.
- Run `python3 -m unittest discover -s tests -v` before claiming done.
- Keep the behavior covered by `tests/test_core.py`.

这三行是“每轮都要”的规则。项目结构、命令大全、历史决策,不该塞这里;它们应该放在 README 或 Skill 里,按需读。


13.3 setting_sources 的默认值是个陷阱

setting_sources 控制加载哪些设置来源:

值加载什么
None(默认)全部:user + project + local
[]不加载任何设置
["project"]项目级:CLAUDE.md、.claude/settings.json、.claude/skills/
["user"]用户目录的设置
["local"]本地私有设置

None 的“全部加载”是 CLI 的默认行为,不是 SDK 的“安全默认”。

在开发者笔记本上,这通常没问题;部署到服务器、CI、多租户环境,就会变成“行为随机器变”。你本地用户目录里的 CLAUDE.md、权限规则、技能,服务器上没有;反过来,服务器上的配置也可能污染你的 Agent。

所以生产环境要显式写:

python
options = ClaudeAgentOptions(
    cwd=str(ROOT),
    setting_sources=["project"],   # 只要项目级
)

setting_sources=[] 不是“关掉一切”,它是“不加载设置来源”。工具、模型、你显式传的 system_prompt 都还在;只是 CLAUDE.md、项目权限、Skill 不会被发现。

这也是第 12 章那个坑的另一面:你把 setting_sources=[] 当隔离,结果 Skill 和 CLAUDE.md 也一起没了。


13.4 get_context_usage:先看钱花在哪

SDK 提供了一个 get_context_usage(),能看上下文各部分的占用:

python
async def show_usage(client):
    usage = await client.get_context_usage()
    print(usage["totalTokens"], usage["categories"])

它不会告诉你“哪句话最有用”,但能告诉你“系统提示、工具、消息、记忆各占多少”。

第 6 章我们是自己数字符;SDK 里用这个接口更准。先看总量和分类,再决定是删工具、缩 CLAUDE.md,还是压缩历史。


13.5 Skill:目录常驻、正文按需

Skill 的本质是“一本手册”:

  • 目录常驻:每个 Skill 的 name + description 在上下文里,模型知道“有这么一本”;
  • 正文按需:模型调用 Skill 工具,才把 SKILL.md 的正文加载进来。

目录大概是这样:

text
taskflow/
  CLAUDE.md
  .claude/
    skills/
      fix-failing-test/
        SKILL.md
      add-feature/
        SKILL.md

fix-failing-test/SKILL.md:

markdown
---
name: fix-failing-test
description: 修复 tests/ 下失败的测试。用户说“修好这个失败测试”“让测试变绿”时使用。不要改测试,改实现。
---
# 修复失败测试

1. 先读 README.md 和失败的测试,确认期望行为。
2. 复现失败,读完整报错。
3. 定位实现,做最小修改;不要改测试。
4. 跑全量测试;失败就回到第 2 步。
5. 总结:改了什么、为什么、还有没有没覆盖的边界。

name 要唯一,description 要写“什么时候用”。模型就是靠这两行决定“要不要翻这本手册”。

挂到 SDK 上:

python
options = ClaudeAgentOptions(
    cwd=str(ROOT),
    setting_sources=["project"],          # 让 CLI 发现 .claude/skills/
    skills=["fix-failing-test", "add-feature"],
    system_prompt={
        "type": "preset",
        "preset": "claude_code",
        "append": "Read CLAUDE.md and the relevant SKILL.md before acting.",
    },
    tools=["Read", "Glob", "Grep", "Edit", "Bash", "Skill"],
    allowed_tools=["Read", "Glob", "Grep", "Skill"],
    permission_mode="acceptEdits",
)

skills 可以是 None(由 setting_sources 决定发现哪些)、"all"、或者名单。名单是上下文过滤,不是沙箱:没列出的 Skill 模型看不见,但这不构成安全边界。


13.6 什么时候用 Skill,什么时候别用

这是这一章最重要的一张表:

需求用什么为什么
步骤固定、结果确定工具 / 代码确定性最高,不靠模型记步骤
步骤多、需要判断、不必每次全讲Skill目录常驻、正文按需,省上下文
一次性的隔离子任务子 Agent独立历史,只回结论
必须拦截/改写/强制Hook在 Harness 层执行,模型绕不过
每轮都要的短规则CLAUDE.md / system prompt常驻,必须每次看到
长文档、项目百科README,按需读常驻太贵,模型也不一定看

几个判断例子:

  • “跑测试” → 工具/命令,不是 Skill。步骤固定,代码执行更可靠。
  • “修失败测试的流程” → Skill。它需要判断:读哪个测试、改哪行、什么时候停。
  • “调研一个库” → 子 Agent。过程长、结论短。
  • “没跑测试不许结束” → Stop Hook。不能靠模型自觉。
  • “不要改 tests/” → CLAUDE.md + 权限。前者提醒,后者兜底。

Skill 不是“提示词模板”。它和工具、子 Agent、Hook 是四种不同的机制;选错了,不是“效果差一点”,是“根本管不住”。


13.7 插件:一箱 Skill 的分发方式

当你有跨项目复用的 Skill,或者要分发给团队,才需要插件。

一个仓库里两个 Skill,不需要插件;.claude/skills/ 就够了。插件解决的是“打包、版本、分发”,不是“让 Skill 生效”。

先别急着做插件。等你有第三个项目也要用同一套 Skill 时,再考虑。


13.8 Skill 的坑

第一,description 每轮都付钱。 正文按需,目录常驻。十个 Skill、每个描述 100 字,就是每轮 1,000 字。Skill 不是越多越好。

第二,正文可能被跳过。 模型看到目录,不一定会调用 Skill。你可以在系统提示里写“先读相关 Skill”,或者把 description 写得足够具体。别假设它一定会翻手册。

第三,白名单不是沙箱。 skills=["..."] 只是让模型看不见其他 Skill;它不阻止模型用 Read 去读 .claude/skills/ 下的文件。要真隔离,用权限和路径。

第四,setting_sources=[] 会把 Skill 一起关掉。 这是最常见的“我配了 Skill 但没生效”。

第五,别把 init 帧的某个字段当“模型可见”。 这次实测里 commands 是 None,但 init 的 data 有 skills、slash_commands、tools;CLI 的斜杠命令列表和模型可见的 Skill 目录不是一回事。要验证,得看真实调用,不是看一个字段。


13.9 跑一遍

完整代码在 code/sdkagent/ch13_skills.py。

bash
python3 -m pip install "claude-agent-sdk==0.2.168"
export ANTHROPIC_API_KEY="..."
cd code
python3 sdkagent/ch13_skills.py

2026-10-21 的最终成功运行(SDK 0.2.168 + CLI 2.1.296 + deepseek-flash):

text
[init] keys: ['agents', 'analytics_disabled', ..., 'skills', 'slash_commands', 'tools', ...]
[tool] Skill {'skill': 'fix-failing-test'}
[tool] Bash {'command': 'ls -la .../taskflow && find ...'}
[tool] Read {'file_path': '.../README.md'}
[tool] Read {'file_path': '.../tests/test_core.py'}
[tool] Read {'file_path': '.../taskflow/core.py'}
[tool] Bash {'command': 'python3 -m unittest discover -s tests -v 2>&1'}
[tool] Edit {'file_path': '.../taskflow/core.py', ...}
[tool] Bash {'command': 'python3 -m unittest discover -s tests -v 2>&1'}
[result] success

结果:先调 Skill,再读 README/测试/实现,跑失败,改实现,跑绿;2/2 通过,没改测试。

这次跑之前,我踩了两个真实的坑:

  1. max_turns=8 不够。 它在“列目录、看 .claude、跑测试”上花了几轮,第 8 轮被 error_max_turns 截断;改成 12 才够。真实模型不是按你的“理想步骤数”走。
  2. Bash 不在 allowed_tools 里,它跑不了测试。 第一次成功改完代码,但测试命令被权限拦住;它只能说“按代码逻辑应该通过”,不能声称跑绿。把 Bash 放进白名单后,才真的跑测试。

这两个坑都不是 Skill 本身的问题,是“上下文/技能”和“权限/轮数”的配合问题。真实运行里,它们经常一起出现。

说明:commands 在这次 init 里是 None;skills/slash_commands/tools 都在 data 里。别拿一个字段推断“模型看不看得见”,要看真实调用。


13.10 下一章:长任务

上下文有了,Skill 有了,但任务一长,还是会满。

第 14 章讲 SDK 里的长任务:自动压缩、PreCompact、外置笔记、子 Agent、resume/checkpoint,以及“哪些是 SDK 自带,哪些还要你自己补”。


本章代码 上下文与 Skill:https://github.com/kevinkelin/claude-agent-sdk-book/blob/master/code/sdkagent/ch13_skills.py 两个 Skill:https://github.com/kevinkelin/claude-agent-sdk-book/tree/master/code/lab/taskflow/.claude/skills 项目规则:https://github.com/kevinkelin/claude-agent-sdk-book/blob/master/code/lab/taskflow/CLAUDE.md 说明:本章代码未在本机运行(环境没装 SDK/凭据);setting_sources 默认值和 Skill 发现规则以你的版本/实测为准。