这篇讲 command / agent / skill 自动触发的优先级、三者分工,以及「用 agent 代替 command」的问题。同一件事用不同扩展做有什么差异,什么时候该选哪一个?这是系列文章的第9篇。
目录概要
- 同一件事、三种写法------"打开时间"的 A/B/C 实验
- Claude 选哪一个:自动触发的优先级规则
- 分工原则:command 是入口、agent 是外包、skill 是工具
- 两种 skill 模式:preload vs on-demand
- 三层编排的真实结构图
- 反面实验:用 agent 代替 command 会发生什么
- 坏味道(过度编排)
- 小结
一、同一件事、三种写法
我做过一个很有意思的对照实验------"显示巴基斯坦当前时间(PKT)"这个需求,同时写成三种扩展:
.claude/commands/time-command.md.claude/agents/time-agent.md.claude/skills/time-skill/SKILL.md
都是读 TZ='Asia/Karachi' date,都是输出同一行时间字符串------实现完全等价 。但三个版本在 Claude Code 的行为空间里扮演的角色完全不同。
1.1 三个版本能干什么
| 维度 | time-command |
time-agent |
time-skill |
|---|---|---|---|
| 用户能手动调吗 | ✅ /time-command |
❌ 不在 / 菜单 |
✅ /time-skill |
| Claude 能自动触发吗 | ❌ 永远需要 / |
✅ 通过 description | ✅ 通过 description |
| 独立上下文吗 | ❌ 共享主上下文 | ✅ 独立 subagent 进程 | ❌ 共享主上下文(除非 context: fork) |
| 有没有 memory | --- | ✅ 可配 memory: user/project/local |
--- |
| 能不能预加载到 agent 里 | ❌ | ❌ | ✅ 通过 skills: 字段 |
| 接受参数吗 | $ARGUMENTS |
prompt 参数 |
$ARGUMENTS |
光看表格就能发现------三者能力差得非常远。"实现一样"不代表"作用一样"。
1.2 用户问"现在几点"会发生什么
现在做一个思想实验:用户不用 /,直接在对话里打一句------
"现在几点了?"
Claude 内部会怎么决定调哪一个?
关键事实------
- time-command 永远不会被自动触发 。Commands 在设计上没有自动匹配的通路,必须用户手动敲
/ - time-agent 和 time-skill 都有自动触发的可能 ,因为它们都有
description字段 - Claude 的偏好 :如果两者都匹配,skill 优先,因为 skill inline 执行没有额外 context 开销;agent 要另开一个上下文窗口,属于"高射炮打蚊子"
选型的本质 :当一个任务既能写成 skill 也能写成 agent 时,默认 skill 。只有在任务需要自主探索、独立上下文或持久 memory 时才升格到 agent。
1.3 如果 skill 关了自动调用呢
disable-model-invocation: true 能把 skill 从"Claude 可自动触发"里摘掉。这时候 Claude 回到上面的决策树------往下找到 agent,触发 time-agent。代价是:本来 inline 一行 bash 就能跑完的事,开了一个独立 context 去做。
如果 skill 和 agent 都禁用自动触发,Claude 就没有扩展可用了------它会回落到自己原生的能力 ,直接生成并执行 TZ='Asia/Karachi' date。
这一路推演下来可以看出------扩展的作用域是"Claude 的默认行为不够好时的补丁" ,它们不是为了绕过 Claude 的能力存在的,是为了在特定场景里比 Claude 的默认做法更优。
二、为什么不把三者合并?
这是作者当初反复问过自己的问题。三个东西都是 markdown + frontmatter,都能"执行一段逻辑"。为什么 Anthropic 不设计成"一种扩展类型 + 不同配置"?
仔细想一下,差异的根源在人和机器谁来触发这件事上------
- Command :用户是触发源 。只有用户敲
/才启动,Claude 不会自己去调。所以它的设计里不需要 description 做匹配、不需要独立 context(因为是用户当前会话里要的东西) - Skill :Claude 是触发源 。Claude 读了 description 决定要不要调。所以 description 是必需 的,context 是共享的(就像是 Claude 临时掏出来用一下工具)
- Agent :既能被 Claude 也能被 Command/其他 Agent 触发。它本身是个完整的"执行体",有自己的工具集、memory、hooks。必须有独立 context(不然调用它的 Claude 会被它的噪声污染)
设计哲学上的权衡 :合并之后意味着一个字段集合要同时表达"用户触发/Claude 触发"、"共享/独立 context"、"工具箱/员工"------这张表会膨胀到没法用。拆成三种,每种专注表达一种角色,反而简单。
这就是 03 篇那个类比的由来------
Command 是按钮、Skill 是工具、Agent 是员工。
按钮按下就响;工具摆在那儿 Claude 想用就用;员工雇来就给他任务让他自己干。三种东西本质不同,勉强合一反而不好。
三、两种 skill 模式:预加载 vs 直接调用
理解了 Command/Agent/Skill 的粗分工,还有一个更细的问题:Skill 到底怎么被使用?
03 篇里那套 release-notes 生成器两种 skill 模式同时出现------一个预加载进 agent,一个被 command 直接调用。这不是随手一摆,是设计上的核心分工。
3.1 预加载(agent skill)
在 agent 定义里的 skills: 字段:
yaml
---
name: release-notes-agent
skills:
- git-log-reader # 启动时 git-log-reader 的全部内容注入到 agent 的上下文
---
git-log-reader 的 SKILL.md 内容在 agent 启动的那一刻就被塞进了 agent 的 system prompt。对 agent 来说,这不是"调用一个工具",而是"我天生就会的东西"。
3.2 直接调用(tool-invoked skill)
在 command 里(或者在 agent 里、在主会话里)用 Skill 工具调:
markdown
# release-notes-crafter.md
Step 3: 调用 release-notes-formatter 排版生成 RELEASE_NOTES.md
Skill(skill: "release-notes-formatter")
release-notes-formatter 不会被任何人预加载------只有调用那一刻才展开。
3.3 两种模式的本质区别
| 维度 | 预加载 | 直接调用 |
|---|---|---|
| 加载时机 | agent 启动时 | 被调用时 |
| 谁能用 | 只有那个 agent | 任何能用 Skill 工具的地方(command、agent、主会话) |
| 消耗 | 整个 session 一直占 context | 只有调用时占 |
| 适用 | 这个 skill 是 agent 的"专业知识" | 这个 skill 是"偶尔用一下的工具" |
类比:
预加载是给员工背的岗位技能 (必须随身带);直接调用是放在工具柜里等人来借的扳手(要用再拿)。
不能搞错------把 release-notes-formatter 预加载到 release-notes-agent 里,结果就是 agent 每次都带着排版模板,但它其实只跑 git log;反过来把 git-log-reader 做成直接调用的 skill,agent 每次用之前都要调用一次 skill 工具,多一轮开销。
四、三层编排的真实结构
看完了理论,回到 03 篇那套 release-notes 生成器的实际结构------
这张图里每一层各司其职:
4.1 Command 层------"对外接口"
release-notes-crafter 做了三件事:
- 接用户输入:问一下目标版本号和起止 tag
- 调 agent:拿 commit 分类数据
- 调 skill:生成排版好的 RELEASE_NOTES.md
它不自己跑 git、不自己写模板------它只做"编排"。对应真实代码:
markdown
# .claude/commands/release-notes-crafter.md
Step 1: Ask the user for the target version and base/head git tags
Step 2: Invoke release-notes-agent via the Agent tool
Step 3: Invoke release-notes-formatter via the Skill tool
简洁、清晰、职责单一。
4.2 Agent 层------"专业外包"
release-notes-agent 是一个有独立上下文的 subagent。关键设计:
yaml
---
name: release-notes-agent
tools: Bash(git *), Read
model: sonnet
memory: project
skills:
- git-log-reader
---
- 独立上下文 :就算
git log返回几百条 commit,也不会污染主会话 - 受限工具集 :只有
Bash(git *)和 Read,别的不给------避免它"自作聪明"去改代码 - 预加载 git-log-reader:Conventional Commits 分类规则、日志字段解析这些知识,直接注入它的 system prompt
从主会话角度看,agent 就是一个黑盒------输入"v1.2.0..v1.3.0",输出"按类型分好类的 commit 列表"。里面怎么搞的不关心。
4.3 Skill 层------"一次性工具"
release-notes-formatter 被 command inline 调用,Markdown 写完就结束。它不是一个常驻 agent,不是一个要人去敲 / 的命令------它是个被调用的工具。
关键点:它需要的数据(分类好的 commit 列表)已经在主上下文里(agent 刚刚返回过来),所以 skill 不需要重新获取------这就是"inline 共享 context"的好处。
4.4 为什么这样分层
这种分层背后有一条很清晰的数据流:
bash
用户输入 → 问 version → 拉 commit → 分类 → 排版 → 输出文件
↑ ↑ ↑ ↑ ↑ ↑
command command agent agent skill skill
每个组件只做一件事。换 git 命令就改 agent、换 Markdown 模板就改 skill、换提示语就改 command------互相不干扰。
五、反面实验:能不能都用 agent?
这是很多人踩过的坑------"既然 agent 最灵活,我全用 agent 不就行了?"
试一下------把 release-notes 生成器改成"主 agent + 子 agent + 孙 agent"三层嵌套:
两个问题立刻冒出来:
问题 1:subagent 不能直接调另一个 subagent
这是 Claude Code 的硬约束。Subagent 调 subagent 需要用 Agent(...) 工具------注意是工具调用,不是 bash 命令。而且嵌套超过一层时,上下文切换开销会翻倍。
这一点项目级 CLAUDE.md 里通常会明确写上:
Subagents cannot invoke other subagents via bash commands. Use the Agent tool (renamed from Task in v2.1.63;
Task(...)still works as an alias)
问题 2:每层都是独立 context
三层嵌套意味着三个独立 context 窗口同时存在,数据要层层手动传递。主 agent 拿到温度值,要传给孙 agent 去画 SVG------这个数据在三个 agent 之间来回塞,成本很高。
对比一下真实实现------
scss
命令 (主会话 context)
├ Agent tool → agent (独立 context, 拿完数据返回)
└ Skill tool → skill (主会话 context, inline 生成)
主会话只开了一个 subagent context,skill 在主会话 inline 跑,数据零拷贝。这就是"正确分工"和"错误分工"的差距。
实用经验 :只在必须独立 context 的地方用 agent。数据转换、格式化、输出生成这类能在主 context 搞定的,一律用 skill。
六、过度编排的坏味道
作者踩过不止一次的坑,总结成几条"闻到这个味儿就退一步想想"的信号:
6.1 三层以上嵌套 agent
不管你的业务多复杂,三层以上 agent 嵌套都意味着设计出了问题。正常场景 1-2 层 agent 已经顶天。
6.2 一个 command 里同时调 5 个 agent
这不是编排,这是调度器。要写调度器去 code 里写,不要在 command 里用 markdown 伪代码写调度逻辑。
6.3 "万能 agent"
见过一个 agent 的 description 写:"Handles all user requests, delegates to sub-agents when needed"------这等于没设计。agent 的 description 要精确到"我只处理 X 类任务",否则 Claude 匹配时会乱套。
6.4 command 里塞业务逻辑
Command 是入口 ,不是业务容器。如果你的 command 有 200 行业务步骤,那 200 行应该拆成 skill 或者 agent------command 只负责调用 和串场。
6.5 skill 里又调 agent,agent 又调 skill,回环
这是最危险的一种------循环调用。Claude Code 不会死循环(有 maxTurns),但 context 会被反复污染,最后谁也看不清到底在干嘛。
一条能自检的小原则:画出调用图,如果有环就是错了。
七、一个小决策树
综合前面所有讨论,给个选型的决策树:
简单说------
- 用户必须手动才能触发? → Command
- 需要隔离或持久记忆? → Agent
- 是某个 agent 的专业知识? → 预加载 Skill
- 其他一切通用工具? → 直接调用 Skill
八、编排之道
这一篇拎出来的主题是"编排"------当你手里同时有 Command、Agent、Skill 三种扩展,怎么组装它们去干一件有点复杂的事。
核心观点三条------
第一,三种扩展不是可以互相替换的。Command 是用户入口,Agent 是外包员工,Skill 是工具。合并后字段会爆,拆开各自专注。
第二 ,Skill 有两种用法------预加载是"背在员工身上的专业",直接调用是"公共工具柜里的扳手"。一个 skill 该用哪种方式,取决于它是某人专属的 还是大家都要用的。
第三 ,正确的编排是"用最轻的方式达成目的"------能 skill 不上 agent,能一层 agent 不上两层。嵌套 / 回环 / 万能 agent 都是过度工程化的信号。
写完这篇,Claude Code 内部怎么用它自己造的扩展这件事基本说清了。但前面说过 Claude Code 不只是"本地工具"------它能连 GitHub、Slack、Notion、各种内部系统,这些"外面的世界 "是怎么插进来的?答案是 MCP(Model Context Protocol)。下一篇把 MCP 拆开看------它的协议层长什么样、它跟 skill/agent 的关系是什么、为什么它让 Claude Code 从"单机工具"跳到了"工作中枢"。