每日了解一个智能体 ------ Claude Code:规则是请求,钩子才是保证
一、先用一个运维场景建立直觉
先看我们这行最熟悉的一件事:怎么保证"变更前必须备份配置"这条规矩被执行。
| 做法 | 怎么说 | 实效 |
|---|---|---|
| 写在制度里 | "《变更管理办法》第 3.2 条:变更前须完成配置备份" | 靠人记。赶时间的时候会被跳过。 |
| 写在流程里 | 变更单上多一个勾选项"□ 已备份" | 好一点,但可以闭眼勾。 |
| 写进系统里 | 变更平台在点击"执行"时自动触发备份,不成功不让往下走 | 这才叫执行。 |
三者的区别不在"有没有说",在"说不说的了算"。
而 Agent 产品里,绝大多数"规矩"都停在第一层和第二层------写在提示词里,写在文档里,写在系统提示词里。
Claude Code 是我在这个系列里看到的第一个把第三层做成一等公民的产品。它的官方文档里有句原话,我第一次读到时就想截下来:
"A rule in CLAUDE.md is a request, not a guarantee."
(
CLAUDE.md里的一条规则是请求 ,不是保证。)
而对应的解法是:PreToolUse hook 能真正阻止一个动作发生。
这就是这篇要拆的核心。
二、三层架构(先看全貌)
Claude Code 的扩展体系官方分成三类,社区常把它画成三层。我按运维好理解的方式重排:
┌──────────────────────────────────────────────────────────────┐
│ ① 指令层(Instruction)------ 描述"这个项目是怎么运作的" │
│ │
│ · CLAUDE.md 项目根,每次会话开头读取 │
│ ★ 官方建议控制在 200 行以内 │
│ ★ 参考资料移到 skills / .claude/rules/ │
│ · Auto Memory 它自己在工作过程中积累的记忆 │
│ · Skills SKILL.md,相关时自动加载 或 /skill-name 调用 │
│ │
│ ⚠️ 这一层是"请求",不是"保证" │
└──────────────────────────────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ ② 护栏层(Guardrail)------ 强制"什么必须发生、什么绝不能发生" │
│ │
│ · Hooks 在生命周期事件上运行脚本/HTTP/提示词/子代理 │
│ ★ 确定性:每次匹配到事件就一定执行 │
│ ★ PreToolUse 可以真正【阻止】动作 │
│ ★ 退出码 2 = 阻塞错误 │
│ · Permissions 按工具/命令/路径/域名设 允许·询问·拒绝 │
│ · Checkpoint 改文件前保存状态,可回滚 │
│ │
│ ✅ 这一层才是"保证" │
└──────────────────────────────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ ③ 编排层(Orchestration)------ 决定"谁去干什么" │
│ │
│ · Subagents 各自上下文 + 各自工具白名单 + 只回传摘要 │
│ · 动态工作流 agent() / pipeline() / parallel() / phase() │
│ · Agent Teams 多个完整会话通过共享任务列表协同(实验性) │
│ · MCP 接外部服务(HTTP 推荐 / stdio 本地) │
│ · Plugins 把上面这些打包,跨仓库复用 │
└──────────────────────────────────────────────────────────────┘
这三层的关系,一句话:
指令层负责"说清楚",护栏层负责"跑不掉",编排层负责"分得开"。
三、逐层拆解:七个值得抄的设计
3.1 CLAUDE.md:项目记忆,以及"200 行原则"
CLAUDE.md 是放在项目根目录的 Markdown 文件,每次会话开头自动读取。装什么?官方文档给的例子是:
- 代码规范
- 目录结构
- 架构约定
- 测试命令
但真正值钱的是官方给的那条限制:
控制在 200 行以内,把参考资料移到 skills 或
.claude/rules/文件里。
为什么要有这条限制? 因为 CLAUDE.md 是每次会话都全量加载的。你写 2000 行,它就每天吃掉 2000 行的上下文额度------而这些内容里 90% 跟今天的活儿没关系。
这跟我第 3 篇讲的 WorkBuddy 技能"按需加载"是同一条原则,只是这里用了一个最朴素的强制执行手段:限字数。
顺带说一句,这跟我们写故障处置手册是一个道理------手册不是越厚越好,是"此刻翻到哪一页"越准越好。
自动记忆(Auto Memory)是另一个值得注意的机制:它会在工作过程中自己积累 项目知识。这跟我第 3 篇讲的三层记忆里的"工作区日志"是同类东西------区别在于,这一层是它自己写的,不是你写的。
用的时候要留个心眼:它自己写的记忆,你得定期看一眼。 万一它记错了某个约定,那个错误会一直带下去。
3.2 Hooks:从"请求"到"保证"(核心章节)
这是整篇最该记住的东西。
Hooks 是在 Claude Code 生命周期的特定事件上运行的脚本 / HTTP 请求 / 提示词 / 子代理。
先看官方给的那个例子------每次编辑完自动跑 Prettier:
json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
四种时机,能力完全不同------这张表一定要看清楚:
| 时机 | 能不能阻止动作 | 用途 |
|---|---|---|
PreToolUse |
✅ 能,真的能拦下来 | 护栏、审批、黑名单拦截 |
PostToolUse |
❌ 不能,动作已发生,只能回报 | 格式化、通知、记录 |
Stop |
✅ 能------跑你的检查脚本,不通过就不让这一回合结束 | 强制验收 |
Notification / 其他 |
--- | 通知类 |
退出码是控制信号:
exit 0 → 通过,继续
exit 2 → 阻塞错误,Claude Code 会停止并显示错误
现在回到开头那个运维场景。如果要在 Claude Code 里强制"改配置前必须备份",写法是这样的:
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "/opt/ops/guard/require_backup.sh"
}
]
}
]
}
}
bash
#!/bin/bash
# require_backup.sh ------ 退出码 2 = 拦截
FILE=$(jq -r '.tool_input.file_path')
if [[ "$FILE" =~ \.(conf|cfg|yaml|yml)$ ]]; then
if ! /opt/ops/backup.sh "$FILE"; then
echo "备份失败,已阻止本次修改:$FILE" >&2
exit 2 # ← 这一行才是"保证"
fi
fi
exit 0
同样是"改之前要备份",写在
CLAUDE.md里是"请记得备份",写成PreToolUsehook 是"没备份就改不了"。这就是制度和技术的区别。
Stop hook 是我认为最有运维味道的一个设计 :回合结束前跑你的检查脚本,不通过就不让这一回合结束。
翻译过来就是:"没通过验收,不许收工。" 我们做变更的都懂这句话的分量。
3.3 Skills:一个正在形成的开放标准
Claude Code 的 Skill 是一个 SKILL.md 文件,相关时自动加载,也可以用 /skill-name 直接调用。
关键信息在这里:Claude Code 的 skills 遵循 "Agent Skills" 开放标准。
这意味着什么?第 3 篇讲的 WorkBuddy 技能,和这里的技能,是同一套标准。
这个系列讲到第 5 篇,出现了第一个跨产品的开放标准 。这是行业成熟的信号------当不同厂商开始共用一套技能格式,说明这个抽象层已经被认可了。
另一个细节:自定义命令已经被合并进 skills------.claude/commands/deploy.md 和 .claude/skills/deploy/SKILL.md 都会创建 /deploy。
社区给的一条实践边界很实用,值得照抄:
把"理解和权衡判断"留在提示词与 skills 里,把"顺序、重试、停止规则"放进代码(hooks / 脚本)里。
理由很实在:Skill 是提示词驱动的,它不保证顺序、不保证重试、不保证可恢复。 需要确定性的时候,别指望提示词。
3.4 Subagents:用 Markdown 文件定义一个工种
子代理就是放在 .claude/agents/ 或 ~/.claude/agents/ 里的 Markdown 文件,带 YAML frontmatter:
markdown
---
name: code-improver
description: Scans files and suggests improvements for readability,
performance, and best practices. Use after writing or
modifying code.
tools: Read, Grep, Glob
model: sonnet
---
You are a code improvement specialist. For each issue you find,
explain the problem, show the current code, and provide an
improved version.
四个字段,各有讲究:
| 字段 | 作用 | 运维视角的类比 |
|---|---|---|
name |
工种名 | 岗位名称 |
description |
什么时候该派它去 | 岗位职责说明(决定派单对不对) |
tools |
工具白名单 | 这个岗位开了哪些权限 |
model |
用小模型还是大模型 | 派初级还是派高级 |
tools 字段是这一节的重点------子代理可以有自己的工具白名单。
我在第 4 篇讲 Manus 时给过 L0--L3 权限分级(只读 / 工作区内可写 / 可执行 / 外部可达)。
这里的
tools: Read, Grep, Glob就是实打实的 L0 只读工位。"派活的时候连权限一起派",这个设计非常对。 你派一个 agent 去"扫描一下有没有问题",它就不该有写权限------不是因为它会乱写,是因为它不需要写。
内置的有三个:Explore (只读代码库搜索)、Plan (规划模式期间做调研)、general-purpose。
3.5 权限模式与 Checkpointing(这里有个必须说的坑)
官方明确的三种模式:
| 模式 | 行为 |
|---|---|
| 手动模式 | 读工作目录内文件通常无需批准;命令执行和文件修改通常会触发确认 |
| Plan 模式 | 以读取和只读探索为主,不修改源文件 |
| auto 模式 | 由后台分类器判断操作是否符合请求 |
还可以针对工具、命令、路径、域名分别设置允许 / 询问 / 拒绝,组织可以通过托管设置集中下发。
注意官方自己强调的一点:具体行为依模式、规则、环境和版本而异,不要笼统地说"每次都弹窗"或"完全不需审批"。
这类话术我在第 2 篇写 Codex 三档审批时也提过。凡是涉及权限的描述,都别用绝对化的说法------版本一变就不成立了。
然后是那个必须说的坑------Checkpointing(检查点):
检查点会在它修改代码前 保存受影响文件的状态,方便恢复。
但是:检查点与 Git 不同,也不能撤销数据库写入、API 调用、部署或其他远程系统副作用。
这句话要加粗记住。 它能帮你回滚文件 ,但回滚不了一次已经发出的 API 调用、一次已经执行的部署。
我们运维对这件事太熟了------"回滚"这个词在不同层面含义完全不同:
- 文件级回滚:容易
- 配置回滚:中等
- 已经生效的外部副作用:几乎不可能
所以第 4 篇那条原则在这里再次成立:真正的治理要在动作发生之前(PreToolUse),而不是发生之后(PostToolUse 只能回报)。
还有一句官方提醒很实在:
即使某项操作得到了权限批准,批准本身也不说明操作符合团队政策或结果正确。
对会产生外部影响的工具,应先理解它将执行什么、作用于哪些资源,再决定是否授予访问。
3.6 Plan Mode 与 /goal:克制与收敛
Plan Mode 是"先探索、再规划、后编码"的落地。Shift+Tab 切换,或 claude --permission-mode plan 启动。它会读文件、给方案,但不改源文件,你还能用 Ctrl+G 编辑它的计划。
官方给的经验法则特别好用:
"如果你能用一句话描述清楚这个 diff,就跳过 plan。"
这句话反过来就是:凡是你要改的东西没法一句话说清,就应该先规划。 我们做变更方案评审的标准,本质上也是这句。
/goal 是给无人值守场景用的:设定一个完成条件,由一个独立的 evaluator 在每一回合之后复查。
"独立 evaluator"这五个字是关键------让另一个角色来判断有没有做完,而不是让它自己宣布做完。
这跟我们"变更实施人和验证人不能是同一人"是同一个规矩。
3.7 动态工作流:六种模式里最值钱的一个
动态工作流提供了几个编程原语:
| 原语 | 作用 |
|---|---|
agent() |
起一个子代理;带 schema 时返回校验过的 JSON,最多重试 5 次 |
pipeline() |
对列表里每一项跑一个 agent,流式过各阶段 |
parallel() |
一组任务同时跑,等全部完成 |
phase() |
在进度视图里分组 |
log() / args |
打印消息 / 传递输入 |
一个重要的失败处理细节 :失败或停止的 agent() 在 pipeline() 或 parallel() 里返回 null,脚本要把这些过滤掉。
这是很实在的工程提醒:并行任务里个别失败是常态,你的代码必须能容忍它,而不是假设全成功。
社区总结出六种常见模式,我挑三个最有用的:
① Classify-and-act(分类再行动)
一个 agent 判断这是什么类型的任务 → 脚本路由到对应处理
适用:分诊(triage)
② Fan-out-and-synthesize(扇出再综合)
每个部分一个 agent → 一个 agent 合并结果
适用:代码库审计
③ ★ Adversarial verification(对抗验证)★
另起一个 agent,专门去【反驳】每一个发现
★ 生产者永远不给自己的作业打分
第 ③ 个是我认为整个系列里最有价值的一个设计思想。
传统流程:
Agent A 产出结果 → Agent A 自己说"我做完了" ❌
对抗验证:
Agent A 产出结果 ──┐
├──► Agent B 专门挑毛病 ──► 收敛后的结果 ✅
(A 不参与评价自己)┘
还有个配套细节:Tournament(锦标赛)模式里,裁判是"两两比较"而不是"打分" 。
理由是:让模型给一个结果打 85 分很难稳定,但让它判断"这两个哪个更好"要可靠得多。
这个洞察太实用了。 我们做方案评审时也是这样------让专家给方案打分会众说纷纭,但"A 方案和 B 方案你选哪个"通常能快速收敛。
在我们这行的直接对应物:
- 对抗验证 = 二次复核 / 交叉验证
- 锦标赛两两比较 = 方案比选
- Loop-until-done(跑到没有新发现为止)= 迭代排查到收敛
四、和第 2 篇 Codex 的对照:两种工程哲学
第 2 篇讲 Codex 时,三方评测给过一个很关键的评价:Codex "比对标产品更急于动手"。
现在可以把它和 Claude Code 放在一起看了:
| 维度 | Codex | Claude Code |
|---|---|---|
| 默认姿态 | 更急于动手 | 先探索 → 再规划 → 后编码 |
| 边界实现 | 三档审批模式 + 工作区边界 | Hooks(强制) + 细粒度权限规则 |
| 子任务 | 最多 8 个并行子 agent | Subagents + 动态工作流 + Agent Teams(实验性) |
| 上下文 | 自动压缩(compaction) | /compact、/context、1M 窗口下约 967K 自动压缩 |
| 项目约定 | AGENTS.md |
CLAUDE.md(限 200 行)+ Auto Memory |
| 技能标准 | --- | Agent Skills 开放标准(与 WorkBuddy 同源) |
| 自证方式 | 跑测试看真实结果 | 跑测试 + 对抗验证(另一个 agent 挑毛病) |
不是谁强谁弱,是两种哲学:
- Codex 的哲学:闭环优先。尽快跑起来,让真实反馈来纠正方向。
- Claude Code 的哲学:边界优先。先把规矩和强制立好,再让它动。
我们运维恰恰需要把这两种揉起来:
- 遇到明确、可验证的任务(跑个脚本、装个依赖)→ 用 Codex 那套,快速闭环;
- 遇到不可逆、影响面大 的任务(改生产配置、删数据)→ 用 Claude Code 那套,先验边界、后动手、外加二次复核。
判断标准很简单:这个动作能不能回滚。能回滚的,快一点没关系;不能回滚的,慢一点是应该的。