作者:互联网 时间: 2026-08-22 08:10:56
设计 Skill 系统,这 3 个坑我替你踩过了需要先看清适用场景和关键步骤,避免只记结论却忽略实际限制。
Skill 是什么?说白了就是一个技能包,核心是一个 SKILL.md 文件。Agent 干活的时候,会根据任务需要按需加载对应的 Skill,比如要做 UI 设计、代码审查,就加载相应的那一个。
听起来是不是挺简单?你可能已经打开 AI 编程工具,准备直接丢一句"帮我实现 Skill 机制"过去。
等一下 ,先别急。 Skill 听着简单,但你真的想清楚怎么在 Agent 系统里实现它了吗?先看这几个问题你能不能答上来:
如果这几个问题你还没想明白,那接下来我就一个一个拆,把整套 Skill 机制的设计讲清楚。
请先查收 Claude Code 官方给的标准Skill头字段:code.claude.com/docs/zh-CN/…
这里我挑几个典型的讲:
| 字段 | 作用 |
|---|---|
name | 展现名称 |
description | 给模型看,决定何时用 |
allowed-tools | 工具白名单 |
disallowed-tools | 工具黑名单 |
model | 限定模型 |
metadata | 自定义键值 |
agent | 指定执行用的 subagent |
context | 执行时是否开辟独立子上下文 |
其中 name 和 description 决定了 Agent 什么时候调用它。
allowed-tools 可能很多人会理解为只允许使用什么工具,实际上是赋予 Agent 执行这些工具的权限,无需你再盯着点同意。
disallowed-tools 则是字面意思,硬性禁止某些工具/命令,即使模型想调也会被系统拦截。
这里有个坑:此次禁用的工具,需要在下一个回合(turn)恢复,否则这些工具会在这个会话里一直被禁用,影响后续操作。。
(turn 是什么,后面文章会专门讲,这里先按这个理解:turn 就是一次会话回合。)
[第 1 回合] 你发消息 → 模型调用某 Skill → Skill 进入 active → disallowed-tools 里的工具被从可用工具池里移除 → 这一回合里,模型根本"看不到" Write / Edit,想调都调不了[第 2 回合] 你发下一条消息 → 限制清除 → Write / Edit 恢复可用disallowed-tools 和 allowed-tools 一样,都是临时作用域。只在调用 Skill 的那个回合预批准
model 则是可以指定这个 Skill 只在特定模型下启用,别的模型加载不到。典型用法有两种:某个 Skill 依赖长上下文或复杂推理,小模型扛不住,就限定它只用大模型,免得跑崩;反过来,简单的 Skill 也可以限定用小模型,省钱。
context 和 agent 则是配合用的:context 决定这个 Skill 是在主对话里跑,还是开辟一个独立子上下文单独跑,agent 决定用哪个子 Agent 来执行。典型场景是:某个 Skill 过程很脏——读几十个文件、跑一堆命令——但你只关心最终结果。这时设 context: fork,让它跑在独立上下文里、不污染主对话,再指定一个子 Agent 去干这票重活。
metadata 则是用来塞任意自定义键值的地方,模型一般不读它。通常是给团队的管理、审计、成本核算用的——比如记 owner、tags、cost-center,Skill 管理平台扫目录时可以按这些字段分类统计。它不影响 Agent 怎么跑,只影响你怎么管。
Skill案例
简单场景,同时也是大多数Skill的头部元信息
---name:frontend-ui-engineeringdescription:构建生产级品质的用户界面。在构建或修改面向用户的界面时使用。在创建组件、实现布局、管理状态,或需要输出看起来达到生产级品质而非“AI生成感”时使用。---复杂一点的:
---name:code-reviewdescription:当用户要求审查代码、检查PR、或查找潜在bug时使用。allowed-tools:-Read-Grep-Bash(gitdiff:*)model:claude-sonnet-4-5disable-model-invocation:falselicense:MITversion:1.2.0metadata:scope:projectagents: [backend-bot, reviewer-bot]---(示例里的 license、disable-model-invocation、version 属于官方标准里的其他字段,基础版可以先不处理。)
结论:如果你实现的是基础 Skill 系统,只处理 name 和 description 就够了;后续要扩展,再基于上面这些字段往上加。
Agent是怎么调用 Skill的 ? 目前有两种主流的做法:
skill_name 加载 SkillSKILL_TOOL = { "name": "skill", "description": "按 name 加载一个已注册的 Skill,返回它的完整指令。", "parameters": { "skill_name": { "type": "string", "description": "要加载的 Skill 名称", "required": True, }, },}defexecute_skill_tool(skill_name: str) ->str: skill = find_skill_by_name(skill_name) # 在注册表里按 name 找ifnot skill: returnf"未找到 Skill: {skill_name}" body = load_body(skill["path"]) # 正文 apply_permissions(skill["meta"]) # 应用 allowed/disallowed-tools# 返回三件套,正文作为 tool result 注入上下文return { "activation": f"<command-name>{skill_name}</command-name>", # 激活标记"base_dir": skill["base_dir"], # 根目录,正文里的相对路径靠它"body": body, # 正文指令 } 注意这个返回值不只是 Skill.md 文件里面的内容,而是三样东西:
<command-name>{skill_name}</command-name>。这是给系统看的——表示"这个 Skill 已被调用",用来做去重和状态追踪(下文作解释),也方便前端展示调用事件。READ_FILE_TOOL = { "name": "read_file", "description": "按路径读取文件内容。", "parameters": { "path": { "type": "string", "description": "文件路径", "required": True, }, },}defexecute_read_file_tool(path: str) ->str: return Path(path).read_text(encoding="utf-8") 顺带补充一个小知识:Skill 的组成不只有 SKILL.md 这一个文件。除了 SKILL.md,还可以带知识文档、可执行脚本、静态资源等:
{skill_name}/├── SKILL.md # 必填:入口(YAML frontmatter + Markdown 指令)├── scripts/ # 可选:可执行脚本(Python / Bash)├── references/ # 可选:长文档、规范、示例└── assets/ # 可选:模板、图标、字体等静态资源所以调用Skill的本质其实是工具调用,使用 读工具 读SKILL.md 或者其他知识文件,使用 Bash 工具执行脚本。
不过在Codex中我倒是发现其内部使用 PowerShell 的 cat 命令获取SKILL.md。

Claude Code 就偏向使用 SKILL_TOOL 完成Skill的加载
第一步系统首先扫一遍指定目录,找到所有 skill文件夹下的SKILL.md,只读头部的 name 和 description,收进注册表中
defregister_skills(skill_dir: str) ->list[dict]: skills = [] for path in Path(skill_dir).rglob("SKILL.md"): meta = parse_frontmatter(path) # 只读头部 name 和 description skills.append({ "name": meta["name"], "description": meta["description"], "path": str(path), }) return skills接着把这些信息注入系统提示词。两种工具对应的注入内容不一样:
Skill 在上下文里的显示大概长这样:
<available_skills><skill><name>pdf</name><description>Comprehensive PDF manipulation toolkit for extracting text and tables, merging/splitting documents, and handling forms.</description><path>/absolute/path/to/pdf/SKILL.md</path></skill></available_skills>(<path> 是给 ReadFile 方式定位文件用的;如果是 Skill 工具方式,路径留在注册表里,不暴露给模型。)
注意,这里只放了 name 和 description,没放正文。这是整个 Skill 系统里最关键的一个设计:注册要轻。 如果这一步就把正文全塞进去,后面的"匹配"和"加载"就没意义了,上下文也会被无关内容占满。
一次任务里,模型可能反复想用同一个 Skill。比如用户连续问两轮"再帮我审查一下代码",模型可能两次都想调 code-review。
如果没有去重,code-review 的正文会被注入两次,白白浪费 token,上下文里还多了份重复内容。
有激活标记 + 去重,系统就能判断:
一句话总结就是:去重 = 防止同一个 Skill 的正文被反复塞进上下文。
系统需要维护一个"当前激活中的 Skill 列表",因为好几件事都靠它:
一句话总结就是:状态追踪 = 系统维护一张"谁正在激活"的表,用来管权限生效和恢复。
放到你的 Skill 系统实现里,大概就是:
active_skills = set() # 当前激活的 Skill 集合defactivate(skill_name): if skill_name in active_skills: return"已激活,跳过"# 去重 active_skills.add(skill_name) # 状态追踪 apply_permissions(skill)defdeactivate(skill_name): active_skills.discard(skill_name) restore_permissions()active_skills 这个集合,同时干了"去重"和"状态追踪"两件事——判断重复靠它,知道该恢复谁也靠它。