让 Agent、Skill、Command 做同一件事,然后放一起会怎样?- Claude9

这篇讲 command / agent / skill 自动触发的优先级、三者分工,以及「用 agent 代替 command」的问题。同一件事用不同扩展做有什么差异,什么时候该选哪一个?这是系列文章的第9篇。

目录概要

  1. 同一件事、三种写法------"打开时间"的 A/B/C 实验
  2. Claude 选哪一个:自动触发的优先级规则
  3. 分工原则:command 是入口、agent 是外包、skill 是工具
  4. 两种 skill 模式:preload vs on-demand
  5. 三层编排的真实结构图
  6. 反面实验:用 agent 代替 command 会发生什么
  7. 坏味道(过度编排)
  8. 小结

一、同一件事、三种写法

我做过一个很有意思的对照实验------"显示巴基斯坦当前时间(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 内部会怎么决定调哪一个?

flowchart TD U["用户: '现在几点了?'"] --> D{Claude 解析意图} D --> M1{匹配到 skill?} M1 -->|描述匹配| S1[time-skill 触发] M1 -->|未匹配| M2{匹配到 agent?} M2 -->|描述匹配| A1[time-agent 触发] M2 -->|未匹配| F[Claude 直接跑 TZ=Asia/Karachi date] M3[time-command] -. 用户没敲 slash 时不触发 .-> D style S1 fill:#afa style A1 fill:#ff9 style F fill:#fcc style M3 fill:#fcc

关键事实------

  • 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 不设计成"一种扩展类型 + 不同配置"?

仔细想一下,差异的根源在人和机器谁来触发这件事上------

graph LR subgraph 触发源 U[User 主动敲] --> C[Command] Claude[Claude 自动判断] --> S[Skill] Claude --> A[Agent] API[另一个 Command/Agent] --> A API --> S end style C fill:#afa style S fill:#aaf style A fill:#ff9
  • Command用户是触发源 。只有用户敲 / 才启动,Claude 不会自己去调。所以它的设计里不需要 description 做匹配、不需要独立 context(因为是用户当前会话里要的东西)
  • SkillClaude 是触发源 。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-readerSKILL.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 两种模式的本质区别

graph TB subgraph 预加载模式 A1[Agent 启动] --> B1[SKILL.md 全文注入 system prompt] B1 --> C1[Agent 的每一轮都能访问这段知识] end subgraph 直接调用模式 A2[主上下文] --> B2[Skill 工具被调用] B2 --> C2[SKILL.md 即时展开] C2 --> D2[执行完毕,context 消化] end style B1 fill:#afa style B2 fill:#ff9
维度 预加载 直接调用
加载时机 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 生成器的实际结构------

graph TB U[用户敲 /release-notes-crafter] --> C[release-notes-crafter<br>Command 层] C --> Q[AskUserQuestion<br>问目标版本号 / 起止 tag] Q --> AT[Agent tool] AT --> A[release-notes-agent<br>Agent 层] subgraph AgentContext[独立 Agent 上下文] A --> PL[预加载的 git-log-reader] PL --> GL[Bash: git log v1.2.0..v1.3.0] GL --> R[返回分类后的 commit 列表] end R --> ST[Skill tool] ST --> S[release-notes-formatter<br>Skill 层] subgraph SkillExec[在主上下文内 inline] S --> OS[输出 RELEASE_NOTES_v1.3.0.md] S --> OM[输出 output.md] end style C fill:#afa style A fill:#ff9 style S fill:#aaf style PL fill:#fcc

这张图里每一层各司其职:

4.1 Command 层------"对外接口"

release-notes-crafter 做了三件事:

  1. 接用户输入:问一下目标版本号和起止 tag
  2. 调 agent:拿 commit 分类数据
  3. 调 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"三层嵌套:

graph TB U[用户] --> MA[主 agent: orchestrator] MA --> SA1[子 agent: release-notes-agent] SA1 --> GA[孙 agent: formatter-agent] style MA fill:#fcc style SA1 fill:#fcc style GA fill:#fcc

两个问题立刻冒出来:

问题 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 会被反复污染,最后谁也看不清到底在干嘛。

一条能自检的小原则:画出调用图,如果有环就是错了


七、一个小决策树

综合前面所有讨论,给个选型的决策树:

flowchart TD Start[我想加一个扩展] --> Q1{谁触发它?} Q1 -->|只有用户手动| CMD[用 Command] Q1 -->|Claude 自动 或 被其他扩展调用| Q2 Q2{任务需要独立 context 或 memory?} -->|是| AGT[用 Agent] Q2 -->|否| Q3 Q3{要被预加载到某个 agent?} -->|是| PS[Preloaded Skill] Q3 -->|否| TS[Tool-invoked Skill] style CMD fill:#afa style AGT fill:#ff9 style PS fill:#aaf style TS fill:#aaf

简单说------

  1. 用户必须手动才能触发? → Command
  2. 需要隔离或持久记忆? → Agent
  3. 是某个 agent 的专业知识? → 预加载 Skill
  4. 其他一切通用工具? → 直接调用 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 从"单机工具"跳到了"工作中枢"。


外部链接

相关推荐
yaocheng的ai分身21 分钟前
【转载】Scaling Managed Agents:将“大脑”和“手”解耦
claude
xcLeigh2 小时前
聚合AI工具KULAAI:GPT、Claude、Gemini、DeepSeek热门模型一键使用
人工智能·gpt·claude·gemini·deepseek·聚合ai·kulaai
Peter·Pan爱编程3 小时前
14. Lambda 表达式:随手可写的函数对象
c++·算法·ai编程
百珏4 小时前
个人理解的AI Code Review 架构的三代演进
架构·aigc·ai编程
人月神话Lee4 小时前
【图像处理】Core Image 与 GPU 渲染管线——让滤镜飞起来
ios·ai编程·图像识别
DO_Community4 小时前
为AI编程降本!OpenCode 原生支持 DigitalOcean 推理路由器
智能路由器·ai编程·claude
麦哲思科技任甲林4 小时前
全变更蒸馏:让AI编程成为一个可进化的系统
人工智能·ai编程·蒸馏·skills·harness工程·回顾
潘锦4 小时前
从带团队到管 AI Coding,方法其实是相通的
ai编程
潘锦4 小时前
AI Coding 时代如何有效度量研发效能
ai编程
名不经传的养虾人5 小时前
从0到1:企业级AI项目迭代日记 Vol.36|临时方案下线,网关区分负载,用量穿透链路——这一周全是“归位”
人工智能·ai编程·ai工作流·企业ai·多agent协作