抓包 Claude Code:system prompt、tools 与 skills 的分层设计(一)
发布时间:
平时一直用 Claude Code,但不搞清楚它背后怎么跟 API 交互的,用起来总觉得不是“知其所以然”,非常难受。于是抓了个包,看看消息结构到底长什么样。后来干脆不自己造轮子了。。一开始用的是 claude-devtools,封装层太多,我想看的原始 JSON 全被包进去了,看不到裸请求。后来换成蚂蚁哪个组的员工写的 cc-viewer(我也参与贡献了🤣),直接看原始api。
简单请求你好,上下文 26k:

- system 不是一段字符串,而是 3 个 text block;
- 用户输入也不是单纯的用户输入,而是 4 个 content block;
- 自带 27 个工具;
- 前 3 个 user content block 是 <system-reminder>,最后 1 个 content block 才是真正的用户输入“你好”;用户输入上还带了 cache_control: { type: “ephemeral” }。
真正的用户输入只是整个请求末尾非常小的一段。Claude Code 的上下文不是“一个 system prompt + 一个 user message”这么简单,而是由 system block、runtime reminder、skill registry、session context、tools schema 和用户输入共同装配出来的。
system 不是一段,而是三个 block

这次完整 input 里,system 字段本身就是一个数组,里面有三个 text block。
第一段是 billing / client metadata:
x-anthropic-billing-header: cc_version=2.1.126.88c; cc_entrypoint=cli; cch=ca1e0;
这第一个比较简单是请求侧的版本、入口和计费/路由元信息。 Claude Code 会把一些 runtime metadata 也放进 system-level input。
第二段是极短的身份声明:You are Claude Code, Anthropic’s official CLI for Claude.
这一段带了:
"cache_control": {
"type": "ephemeral"
}
第三段才是传统意义上我们会称为 system prompt 的主体,包含软件工程任务定位、安全边界、不要猜 URL、工具使用规范、输出风格、risky action 的确认策略、session-specific guidance、auto memory、environment 和 context management。也就是说,system 层本身也被拆开了:metadata block、identity block、main instruction block。
system 是 3个 block,runtime 就有机会对不同 block 做不同生命周期管理。版本信息、身份声明、主规则、环境信息,不一定要被视为同一种上下文。它们的变化频率不同,缓存价值也不同。
user message 也不是用户输入,而是 runtime injection 容器

虽然 role 是 user,但里面前 3 个 content block 都不是真正用户说的话,而是 Claude Code harness 注入的 <system-reminder>。
第一段是 SessionStart hook 注入的 using-superpowers 全文。它以这种形式出现:

<system-reminder>
SessionStart hook additional context:
<EXTREMELY_IMPORTANT>
You have superpowers.
Below is the full content of your 'superpowers:using-superpowers' skill...
</EXTREMELY_IMPORTANT>
</system-reminder>
第二段是 skill registry,列出了当前可用 skills,例如 update-config、keybindings-help、simplify、fewer-permission-prompts、loop、schedule、claude-api、superpowers:using-git-worktrees、superpowers:test-driven-development、superpowers:systematic-debugging、superpowers:brainstorming、review、security-review 等。

第三段是 session context:

<system-reminder>
As you answer the user's questions, you can use the following context:
# currentDate
Today's date is 2026-05-17.
IMPORTANT: this context may or may not be relevant to your tasks.
You should not respond to this context unless it is highly relevant to your task.
</system-reminder>
但是这个我感觉有问题呀,有日期,所以kv cache最多24小时必定失效🤣日期更新的那一刻系统压力会特别大。
第四段才是真正用户输入:

你好
而且这段带了:
"cache_control": {
"type": "ephemeral"
}
这说明Claude Code 的 user message 在协议层虽然是 role: “user”,但在 runtime 语义上承担了多个角色:bootstrap policy injection、skill registry injection、session context injection 和 actual user input。所以不能把 messages 简单理解成“用户说过的话”。在 Agent runtime 里,messages 也是 harness 注入上下文的容器。
启动上下文从 4 万到 26.8K
之前发一句“你好”,请求上下文也能占到 4 万 token 左右。这次同样是非常简单的输入,token 统计变成了:
input: 26.8K
output: 116
这比之前少了很多。之前 4 月的时候我还有截图,是 4 万,Claude Code 在做启动上下文瘦身,把 runtime context 拆得更细,并且更依赖按需加载和缓存。一个 Agent CLI 的启动上下文里通常有几类重东西:main system prompt、tools schema、bootstrap policy、skills registry、project-level instructions、memory system instructions、environment、current date、user input。如果这些东西全部无差别拼在一起,一个“你好”也会背上完整运行时成本。
这次降到 26.8K,说明 Claude Code 至少在几个方向上做了优化。减少默认注入内容,不是所有项目上下文、所有 skill 全文、所有历史信息都默认塞进来。并且把 runtime context 拆成多个 block,system 拆成多个 system text block,user content 里也拆成多个 reminder block。给不同 block 留出 cache control 的空间。抓包里可以看到部分 block 带了 cache_control,这至少说明 Claude Code 在请求结构上已经把缓存边界作为上下文装配的一部分,而不是把 prompt 当成纯文本。然后把真正用户输入放到最后,这对 prefix cache 很关键:稳定内容在前,动态输入在后,才能最大化前缀复用。
using-superpowers 是 bootstrap policy

using-superpowers 是特殊例外。它不是只出现在 skill registry 里,而是在 SessionStart hook 里全文注入,它本质上是 bootstrap policy。普通 skill 可以通过 Skill tool 加载,但 using-superpowers 的职责是告诉模型:你有 skills;你必须先检查 skills;如果有 1% 可能相关,也要调用 Skill tool。如果它自己也需要通过 Skill tool 才能加载,就会有启动悖论:模型需要先知道“应该调用 Skill tool”,才能加载 using-superpowers;但模型又需要 using-superpowers,才知道“应该调用 Skill tool”。所以它必须在会话开始时直接注入。这次抓包里,第一段 user-side <system-reminder> 就是它的全文。这说明 Claude Code 对 skills 体系做了一个 bootstrapping 设计:bootstrap skill 全文注入,普通 skills 只注入索引,需要时再物化全文。这是一种很典型的 runtime 分层:少量启动策略常驻,大量长尾策略延迟加载。
using-superpowers 里写得非常强硬:
If you think there is even a 1% chance a skill might apply...
YOU ABSOLUTELY MUST invoke the skill.
但它依然只是自然语言上下文。这意味着它只能改变模型行为概率,不能保证模型一定执行。
我之前看到的日志里就有例子:我让 Claude Code 看两个仓库的代码结构。按 using-superpowers 的规则,哪怕只有 1% 可能相关,也应该先调用 Skill。但 assistant 的 thinking 直接是 “Let me explore both projects”,然后发了两个 Bash tool_use 就开干了。这说明,把策略写进 <system-reminder>,不等于策略真的变成了硬约束。如果你希望“所有任务必须先过 skill router”,工程上就不能只靠这段 skill 文本。应该在 harness 层做强制流程,比如第一步必须进行 skill selection,未完成 skill selection 前禁止调用 Bash/Edit,或者由外部 router 先决定应该加载哪些 skills,再把已加载 skill 交给模型执行。这才是 prompt 约束和 runtime 约束的分界线:natural language prompt = soft prior,tool schema = structured action space,harness / validator = hard constraint。
system-reminder 的角色:不是 system,但承担 system-like runtime instruction

这次抓包里,三个 <system-reminder> 都位于 role: “user” 的 message 里。所以从 API role 来看,它们不是 system prompt。但从语义上看,它们又确实在承担 system-like instruction 的作用。
这类内容有几个特点:它不是用户自然输入,用户只说了“你好”,并没有说自己有哪些 skills,也没有说今天日期是多少;它不是主 system prompt,主 system prompt 在 system 字段里,包含 Claude Code 的基础行为规则;它是 harness 注入的 runtime context,来自 SessionStart hook、skill discovery、runtime date provider 等系统组件。所以 <system-reminder> 更准确的定位是 message-layer runtime instruction。它位于 messages 里,但不是用户真实意图;它像 system,但不是 API system role;它由 harness 注入,服务于当前 session 的动态上下文装配。这也是 Agent runtime 里一个很重要的边界:API role 和语义角色不是一一对应的。role: “user” 不一定全部是用户说的话;system-reminder 不在 system 字段里,但可能对模型行为产生强约束倾向;tools 不在 prompt 文本里,但定义了结构化动作空间;skills 不在 tools 里,但可以通过 Skill tool 被物化成上下文。
下一篇将继续拆解 skills 的加载位置变化、26 个 tools 的固定成本与缓存 trade-off、Skill tool 作为 context materialization API 的精妙设计、cache_control 的 block-level 缓存策略,以及 system prompt / tools / skills / reminders / cache 五层分工的完整总结。