核心三角-Command Agent Skill - claude_0x02

45 阅读8分钟

目录概要

  1. 接着上一篇的疑问
  2. 历史背景:为什么会演化出三个相似的东西
  3. 一张表看清三者本质
  4. 三个维度深入对比
  5. 经典场景:"现在几点了?"——谁响应?
  6. 决策树:遇到需求到底选哪个?
  7. Frontmatter 对比速查
  8. 混编:三者如何组合使用

1. 接着上一篇的疑问

上一篇扫完 6 个核心概念之后,我留了一个尾巴——Command、Agent、Skill 这三个东西看起来都是一段提示词,都能封装工作流,到底有啥区别?这篇就把这个尾巴接上。

这三者不仅名字容易混,frontmatter 字段还有大量重叠——都有 namedescriptionallowed-toolsmodel——光看一个文件内容,你甚至分不出这是 command 还是 skill。更要命的是,它们的触发时机、上下文隔离、状态持久化等行为差异,在官方文档里是分散描述的,你很难在一个地方拿到清晰对比。

这篇的三个部分:

  • 历史——为什么会演化出三个,不是一个
  • 对比学习——三者在各维度上的差异
  • 选择决策——遇到需求怎么判断用哪个

2. 历史背景:为什么会演化出三个相似的东西

不感兴趣历史的可以直接跳到第 3 节。但我个人觉得,理解演化路径之后,三者的定位会清晰很多——它们不是拍脑袋造的,是一步一步解决真实问题迭代出来的。

2.1 最早只有 Commands

Claude Code 最早的扩展机制只有 slash commands

那时候很朴素:想复用工作流就写个 .md,里面放提示词,然后用 /name 触发。跟 textexpander、shell alias 都没啥区别。

graph LR
    U[用户输入 /commit-and-push] --> C[读取 commit-and-push.md]
    C --> P[作为提示词塞给 Claude]
    P --> Cla[Claude 执行]

Commands 解决的问题很直白:避免重复打字。但很快就暴露出短板:

  • Claude 不会主动用:你不输入 /xxx,Claude 永远想不到去用
  • 污染上下文:一个复杂 command 几百行提示词塞进主对话,越用越慢
  • 没有记忆:每次白板开始,学不到东西

2.2 Subagents 诞生——解决复杂任务

Boris Cherny 在一些公开访谈里反复提到一个观点:不要让一个复杂任务污染主对话

为了这件事,Subagents 诞生了。Subagent 的设计哲学是:

一个 subagent 就是一个有独立人格、独立上下文、独立工具权限的"员工"。

graph TB
    Main[主对话上下文] -.派遣任务.-> Agent[Subagent<br/>独立上下文]
    Agent -.只返回结果.-> Main
    Agent --> T[自己的工具集]
    Agent --> M[自己的 memory]
    Agent --> S[自己的 skills]

它解决了:

  • 隔离:子任务不污染主对话
  • 自主:Claude 自己判断什么时候派遣 subagent(靠 description 匹配)
  • 专业化:每个 subagent 有专属工具、模型、权限

但 Subagents 太重了。对于"现在几点了?"这种问题,启动一个独立上下文的 agent,就像叫一个专职员工去接一杯水——成本完全不匹配。

2.3 Skills 诞生——轻量级可复用知识

Anthropic 内部用 Claude Code 时发现,大量的场景是:

"我想让 Claude 做某件事时按某个方法做,但不值得起一个 agent。"

比如:

  • 生成 SVG 时按公司设计规范
  • 调用内部 API 时按错误处理约定
  • 写 commit message 时按项目风格

这些都是可复用的知识片段,不是独立任务。于是 Skills 出现了。

Skills 的关键创新是"渐进式揭露"——一个 skill 是一个目录,不是单个文件。SKILL.md 只放"何时用、怎么用"的摘要,具体参考资料、脚本、示例分散在子目录里,用的时候再读。

graph TD
    A[SKILL.md<br/>简述 + 何时触发<br/>默认加载]
    B[references/api.md<br/>用时才加载]
    C[scripts/validate.py<br/>用时才执行]
    D[examples/case1.md<br/>参考示例]

    A -.用到时读.-> B
    A -.用到时跑.-> C
    A -.用到时看.-> D

2.4 为什么不把三者合并?

我其实也想过这个问题——为什么不设计一个统一的 "Extension" 类型,用几个布尔字段开关?

答案是:三者的心智模型完全不同,强行合并反而更难学

  • Command 是"用户主动触发的工作流入口"——心智模型是按钮
  • Skill 是"可复用的知识/流程"——心智模型是工具箱里的工具
  • Agent 是"有独立人格的员工"——心智模型是员工

按钮、工具、员工,这三个词你一听就知道用法不同。但如果把它们合成一个叫 "Extension" 的东西然后用布尔字段区分,反而让人每次都要脑补"这东西现在是按钮还是员工"。

这跟编程语言设计里"正交性 vs 简单性"是一个老问题——Anthropic 选择保留三个清晰的心智模型,承担额外的概念负担换取更低的使用负担。


3. 一张表看清三者本质

这张表是全篇精华,建议截图保存。

维度CommandAgentSkill
心智模型按钮员工工具
文件位置.claude/commands/<name>.md.claude/agents/<name>.md.claude/skills/<name>/SKILL.md
运行上下文内联(共享主对话)独立子上下文内联(默认)
用户主动触发/command-name❌ 不出现在 / 菜单/skill-name(除非 user-invocable: false
Claude 自动触发❌ 永远不会✅ 通过 description 匹配✅ 通过 description 匹配
独立上下文窗口✅ 完全隔离❌(除非 context: fork
预加载其他 skillsskills: 字段
持久化 memorymemory: user/project/local
可限制工具权限allowed-tools:tools: / disallowedTools:allowed-tools:
可指定模型
典型场景团队共享工作流入口复杂自主任务可复用的知识片段

4. 三个维度深入对比

4.1 上下文维度

这是三者最本质的区别。

graph TB
    subgraph Main[主对话 - 用户与 Claude]
        direction LR
        M1[用户消息]
        M2[Claude 响应]
        CMD[Command 执行<br/>内联在主上下文]
        SKILL[Skill 执行<br/>内联在主上下文]
    end

    AGT[Agent 执行<br/>独立上下文]
    Main -. 派遣任务 .-> AGT
    AGT -. 只返回结果 .-> Main

    style AGT fill:#faa,stroke:#f00
    style CMD fill:#afa,stroke:#0a0
    style SKILL fill:#aaf,stroke:#00f

一个权衡:

  • 内联(Command/Skill):快、轻,但会占用主上下文 token
  • 隔离(Agent):慢、重,但不污染主上下文

一条实用经验——如果一个任务的中间推理过程很长(比如读十几个文件才能下判断),用 Agent;如果只是"应用一下模板"或"按某个方法做一件事",用 Skill。

4.2 触发维度

flowchart TD
    User[用户说 '现在几点了?']
    User --> Check{Claude 决策}
    Check --> C1[检查 Commands<br/>用户有输入 /xxx 吗?]
    Check --> C2[检查 Skills<br/>有 description 匹配?]
    Check --> C3[检查 Agents<br/>有 description 匹配?]

    C1 -->|没| X1[Commands 不触发]
    C2 -->|time-skill 匹配| Y1[✅ Skill 候选]
    C3 -->|time-agent 匹配| Y2[Agent 候选]

    Y1 --> Pick[Claude 选最轻的]
    Y2 --> Pick
    Pick --> Use[用 time-skill]

    style X1 fill:#fcc
    style Y1 fill:#cfc
    style Use fill:#ff9

Claude 的默认优先级

  1. Skill(内联,最便宜)← 优先
  2. Agent(独立上下文,贵)← Skill 不够用时
  3. Command(永不自动触发)← 只等用户主动输入

4.3 状态维度

CommandAgentSkill
单次执行内的状态
跨会话记忆memory 字段
预加载其他知识skills 字段

唯一有持久化人格的是 Agent。这也是为什么 "code reviewer" 这种长期积累风格偏好的角色,最适合做成 Agent。


5. 经典场景:"现在几点了?"——谁响应?

把"报时"这一个需求分别实现成三种扩展(有些是演示用的),就有了完美的对照组:

  • time-command(Command)
  • time-agent(Agent)
  • time-skill(Skill)

当用户说"现在几点了?"时:

机制会触发吗?为什么?
time-commandCommand 不会自动触发,必须输入 /time-command
time-agent候补description 也匹配,但 Skill 更轻——主对话直接内联算出来就够了,没必要开独立上下文。Skill 存在时,Agent 优先级更低
time-skill首选description 匹配 + 内联执行 + 零上下文开销

如果你把 "time" 这个需求实现成 agent 而不是 skill,那就是高射炮打蚊子。


6. 决策树:遇到需求到底选哪个?

flowchart TD
    Start[新需求来了] --> Q1{用户主动输入 /slash<br/>才触发吗?}

    Q1 -->|是| CMD[选 Command<br/>工作流入口]
    Q1 -->|否| Q2{任务复杂度?}

    Q2 -->|轻量 单一能力| Q3{需要隔离上下文?}
    Q2 -->|复杂 多步自主| Q4{需要持久化记忆<br/>或预加载知识?}

    Q3 -->|否| SKILL[选 Skill]
    Q3 -->|是| SKILL_F[选 Skill<br/>设置 context: fork]

    Q4 -->|否| AGT[选 Agent 基础版]
    Q4 -->|是| AGT_P[选 Agent<br/>+ memory + skills]

    style CMD fill:#afa
    style SKILL fill:#aaf
    style SKILL_F fill:#aaf
    style AGT fill:#faa
    style AGT_P fill:#faa

6.1 实战四问

遇到新需求时,顺次问四个问题:

谁触发?

  • 用户主动 → Command
  • Claude 判断 → Skill / Agent

任务多重?

  • 几十 token 的轻操作 → Skill
  • 多步推理、读大量文件、调 API → Agent

要不要记住东西?

  • 每次白板 → Skill / Command
  • 跨会话记忆 → Agent(memory 字段)

会不会被 agent 调用?

  • 是 → 做成 Skill(可通过 skills: 预加载到 agent)
  • 否 → 根据其他条件决定

7. Frontmatter 对比速查

三个 frontmatter 有很多字段重名,但语义不同。这里只给最小常用集——完整的 13 个 Command 字段会在 04 篇逐一拆解,Agent/Skill 的完整字段表也会在对应的深入篇补齐。这里够用就行。

Command

---
description: Do something useful
argument-hint: [issue-number]
allowed-tools: Read, Edit, Bash(gh *)
model: sonnet
---

(还有 disable-model-invocationuser-invocablename 等另外 9 个字段——04 篇细讲。)

Agent

---
name: my-agent
description: Use this agent PROACTIVELY when...
tools: Read, Write, Edit, Bash
disallowedTools: Bash(rm *)
model: sonnet            # haiku / sonnet / opus / inherit
maxTurns: 10
permissionMode: acceptEdits
memory: user             # user / project / local
skills:
  - my-skill
mcpServers:
  - playwright
background: false
isolation: worktree
color: cyan
---

Skill

---
name: my-skill
description: Do X when user asks for Y
argument-hint: [file-path]
disable-model-invocation: false
user-invocable: true
allowed-tools: Read, Grep, Glob
model: sonnet
context: fork
agent: general-purpose
---

字段含义对照(同名不同义)

字段CommandAgentSkill
name文件名必须显式声明显式或文件夹名
description描述(可选)触发匹配用,必写触发匹配用,必写
allowed-tools允许的工具tools 字段允许的工具
model模型别名模型别名或 inherit模型别名

8. 混编:三者如何组合使用

最经典的组合是 Command → Agent → Skill 三层编排。

sequenceDiagram
    actor User as 用户
    participant CMD as /release-notes-crafter<br/>(Command)
    participant AGT as release-notes-agent<br/>(Agent)
    participant PS as git-log-reader<br/>(预加载 Skill)
    participant GIT as 本地 git 仓库
    participant SK as release-notes-formatter<br/>(Skill)

    User->>CMD: /release-notes-crafter
    CMD->>User: since-tag? target-version?
    User->>CMD: v1.1.0, v1.2.0

    rect rgb(250, 200, 200)
    Note over CMD,AGT: Agent 阶段 独立上下文
    CMD->>AGT: Agent 工具调用
    AGT->>PS: 读取预加载指令
    PS-->>AGT: git log 命令 + 分类规则
    AGT->>GIT: git log v1.1.0..HEAD
    GIT-->>AGT: 48 条 commit
    AGT-->>CMD: feat/fix/breaking 分桶
    end

    rect rgb(170, 170, 255)
    Note over CMD,SK: Skill 阶段 内联执行
    CMD->>SK: Skill 工具调用
    SK->>SK: 按模板排版 + 写文件
    SK-->>User: RELEASE_NOTES_v1.2.0.md
    end

这里用了两种 skill 调用模式:

  1. Agent Skill 模式git-log-reader 通过 agent frontmatter 的 skills: 字段预加载到 agent 上下文
  2. 直接 Skill 模式release-notes-formatter 由 command 通过 Skill 工具直接调用

这两种模式同样是 skill,使用方式完全不同——这在下一篇拆 release-notes 生成器源码时会看得很清楚。


三者的分工

到这里,Command/Agent/Skill 三者的差异和选型逻辑就讲完了。

简单复盘一下——三者不是三个并列的功能,而是演化路径上的三个阶段:Commands 解决"少打字",Subagents 解决"任务隔离",Skills 解决"轻量知识复用"。三个心智模型各司其职——按钮、员工、工具,你记住这三个词,基本不会选错。

选型四问也很直白:谁触发?多重?要记忆吗?被谁调用? 顺次答完就有答案。

抽象讲完了,该看具体执行了。


Footnotes

参考资料

外部链接