技能、子智能体、MCP 还是钩子:到底该用哪个?
必须每次都发生的事用钩子;智能体需要触达一个它原本碰不到的系统,用 MCP;它需要知道你们怎么做一件它本来就会做的事,用技能;这项工作会淹没主上下文,用子智能体;只有每一轮都需要的东西才写进指令文件——而插件只是把以上任意几样打包发出去的方式,不是第六个选项。这条规则成立的原因,并不是大多数对比文章给出的那个。上下文成本——现有建议大多以它为轴——正在被工程手段消解;剩下的那根轴是谁来扣扳机。只有钩子的运行不取决于模型的选择;其余一切,无论你措辞多么强硬,都只是请求。
答案,写成一套可以照着走的流程。
按这个顺序提问。顺序是要紧的:第一个回答「是」的问题就决定了结果,而排在前面的问题,选错的代价是丢掉一项保证,而不只是多花几个 token。
# Stop at the first yes. 1. Must it happen every time, whatever the model thinks? yes -> HOOK # lint after edit, block a path 2. Does the agent need a system it cannot reach today? yes -> MCP server # database, tracker, Slack 3. Would the work bury the main thread in output you will never read again? yes -> SUBAGENT # wide search, log triage 4. Is it a procedure or reference the agent needs only for some tasks? yes -> SKILL # release checklist, API guide 5. Does every turn of every task need it? yes -> AGENTS.md / CLAUDE.md / rules 6. Will another repo or team need the same setup? yes -> wrap the above in a PLUGIN
同样这六样,并排放在一起。「由谁触发」这一列,正是本文其余部分要讲的。
真正的那根轴,是谁来扣扳机。
大多数对比按内容是什么来分类——知识、能力、自动化。这种分法没错,却几乎没用,因为人们实际犯的错,并不是把知识放错了格子,而是把一项要求放进了一个只能交付建议的地方。
- 始终。指令文件每一轮都会被读到。它始终在场,但这不等于始终被遵守;模型会把它和窗口里的其他一切放在一起权衡。
- 模型的判断。技能在模型判定你的任务匹配其描述时载入。MCP 工具在模型决定调用它时运行。在 Claude Code 和 Cursor 中,子智能体在模型判定任务适合它时启动。三种不同的机制,同一个触发器:一次预测。
- 一个事件。钩子触发,是因为框架走到了
PreToolUse或PostToolUse,而不是因为谁选择了它。它是这张清单上唯一一个模型既不能选择、也无法跳过其触发的机制。 - 一个人。敲下
/deploy、@ 提及一个子智能体、或点名一项技能,都把扳机从模型手里挪到了你手里。Claude Code(disable-model-invocation: true)和 Codex(allow_implicit_invocation: false)都允许你把一项技能设为只能由人调用。
Anthropic 自己的功能指南把后果说得毫不含糊:写在 CLAUDE.md 或技能里的「绝不要编辑 .env」这类指令,「是请求,不是保证」,而一个拦下这次编辑的 PreToolUse 钩子才是强制执行。凡是带合规、安全或数据丢失色彩的事,这句话就是全部的决定。如果得知模型在五十次运行里漏掉过一次会让你不高兴,那么决定它是否运行的,就不能是模型。
反过来同样要紧。钩子不会推理。「每次编辑后运行格式化工具」是钩子;「把 linter 抱怨的问题都修好」是技能,因为决定怎么修是判断。常见的好配置是两者兼用:一个 PostToolUse 钩子运行 linter 并把输出回传,一项技能说明拿这些输出该怎么办。
上下文成本曾经决定这件事,如今大体上不再是了。
那条耳熟能详的建议——「别把流程包成 MCP 服务器,每个工具定义都会待在每一次请求里」——在写下的时候是对的。此后,各家框架已经把这笔成本从几乎所有地方挪走了,只剩下始终加载的那一档。
- MCP 的 schema 被推迟载入。Claude Code 的文档现在写明工具搜索默认开启:会话开始时只载入工具名称和服务器说明,完整的 JSON schema 留在窗口之外,直到 Claude 需要某个具体工具。一台闲置的服务器几乎不花钱。
- 技能正文被推迟载入。Claude Code 和 Codex 都先载入技能的名称和描述,只在它被选中时才载入正文;Cursor 则把它的技能描述为「在相关时」载入——这就是智能体技能里讲的渐进式披露。
- 子智能体把 token 花在别处。它们总量上更贵——Codex 的文档直说子智能体工作流比可比的单智能体运行消耗更多 token——但这笔花销落在一个用完即弃的窗口里,这正是要点所在。
- 钩子不花任何上下文,除非它返回输出。一个把 400 行 lint 报告打进对话的钩子,已经把自己变成了一份昂贵的指令文件。
- 指令文件是例外。它的全文在会话开始时载入,并留在每一次请求里。Anthropic 的建议是把 CLAUDE.md 控制在 200 行以内,把参考资料移进技能——这是唯一一档行数仍是主要代价的地方。
留在每一次请求里的,是一串描述——技能描述、子智能体描述、工具名称。这把失败模式从成本转到了检索:描述含糊,模型就会挑错技能或一个都不挑,这正是工具文档与可发现性所处理的问题。这也给出了规模上的提示:子智能体描述合计超过 15,000 个 token 时,Claude Code 会在启动时发出警告。
同一个名字,在不同工具里不代表同一个触发器。
既然要紧的是触发器,就要逐个框架去核对。名称已经趋同,行为并没有。
- 子智能体。Claude Code 在任务匹配某个子智能体的
description时会自行委派,Cursor 也说它的智能体基于同样的依据「主动委派」。Codex 的本地版本则只在收到直接请求、或项目文件或技能中有指令要求时才会派生智能体。把同一份code-reviewer定义在它们之间搬来搬去,它就从模型会主动伸手去用的东西,变成了没人吩咐就没人调用的东西。 - 钩子是确定的,但不是完整的。它们每次都会在自己的事件上触发,但只针对经过钩子的那些路径。Codex 写明,网页搜索这类托管工具不走本地钩子路径,并告诉你要「把工具钩子当作一道有用的护栏,而不是完整的强制边界」。Cursor 通用的
preToolUse在 schema 里接受ask,却并不执行它。钩子保证自己会运行,不保证自己能看到一切。 - 继承随子智能体一起来。在 Codex 中,子智能体继承父级的沙箱与审批设置,自定义智能体文件无法替换它们;在 Cursor 中,它们继承全部工具,包括 MCP。隔离的是上下文,不是权限——这道缝隙在子智能体究竟继承了什么里被拆开来讲。
- 指令文件大体趋同。Codex 和 Cursor 读 AGENTS.md;Claude Code 读 CLAUDE.md。用哪个文件,是本页上最无关紧要的选择,正如AGENTS.md vs CLAUDE.md vs Cursor 规则 vs 技能所论证的。
五种看似合理、实则放错了位置的做法。
- 把必须守住的规则写进指令文件。AGENTS.md 里的「绝不要推送到 main」一直管用,直到某次长会话里它不管用了。为了模型留着这句话,同时加一个拒绝该命令的
PreToolUse钩子。 - 把流程包成 MCP 服务器。如果服务器的工具只是返回一段指令,你造出来的就是一项多了一跳网络、一个要保持运行的进程和一道第三方信任边界的技能。MCP 的价值在于另一端确实有东西要去触达——智能体技能里的那条规则依然成立,只是如今的理由不再是 token。
- 为一个人设开子智能体。一个读三个文件就作答的「资深评审」子智能体,换来的是一个你并不需要的额外上下文窗口,和一份你无法核查的摘要。人设是技能;子智能体是给那种阅读量会把你淹没的情形用的,正如子智能体所论证的。
- 有副作用的技能仍允许模型调用。一个模型能靠匹配描述触发的
/deploy技能,就是一次模型可以自己决定执行的部署。把它设为只能由人调用。 - 把插件当技能来装。插件可以携带钩子,而钩子会以你的权限、在模型从未察觉的事件上运行你的命令。Claude Code 会合并来自所有来源的钩子,而不是让某一个覆盖另一个,所以插件的钩子会和你自己的一起触发。安装插件就是安装自动化;生命周期钩子与框架配置讲了为什么这条路径的变更管控最少,Agent Plugins 发布时没有信任模型则讲了这份共享格式漏掉了什么。
真实的活儿通常要两种机制,按触发器分工。
有用的做法,是把一项工作中与某个机制的触发方式相符的那部分交给它,而不是把整项工作硬塞进一个机制里。
- Lint 与格式化。钩子:每次编辑后运行格式化工具和 linter,只回传失败项。技能:你们团队如何解决那些反复出现的问题。
- 与数据库对话。MCP:连接本身,配只读凭据。技能:schema、命名上的怪癖,以及人人都会写错的那三条查询。如果写操作必须被拒绝而不只是被劝阻,就在这个 MCP 工具上加一个钩子。
- 代码评审。技能:检查清单,点名调用。子智能体:在大型 diff 上运行它,让主线程拿到的是结论,而不是 40 个文件的阅读量。
- 发布。流程用只能由人调用的技能;绝不能跳过的步骤用钩子,比如在本次会话中测试尚未通过时拒绝打 tag。
- 第二个仓库。把这一整套打包成插件——并像审查一次 CI 变更那样审查它的钩子,因为它们就是 CI 变更。
这周就盘点一下手上的东西:把 AGENTS.md 或 CLAUDE.md 里每一句以「绝不」或「始终」开头的话列出来,逐条问一次:漏掉一次会不会造成伤害。会的,变成钩子;大段参考资料变成技能;需要连上活系统的,变成 MCP 服务器;总是把上下文塞满的任务,交给子智能体。然后在你实际使用的框架里逐个核对触发方式,因为同一份文件,在一个工具里会被模型调用,在另一个工具里却纹丝不动。按「必须由谁决定它发生——模型、事件,还是你」来选,正确的格子自然就出来了。
相关阅读:协议本身见什么是 MCP,把服务器一侧做好见MCP 工具设计,书面规则为何始终只是建议见指令层级,始终加载那一档背后的算术见上下文预算。