🚄 前言
上篇你已手动完成 Skill 从设计到「双 Agent」迭代的全过程。但 Skill 仍像躺在磁盘里的一组 Markdown——Agent 怎么发现它、加载它、在真实任务里调用它? 推广到团队后,如何避免 「一个人的成功,十个人的混乱」?
4 让你的 Agent 使用和生成 Skill
到目前为止,你手动完成了 Skill 从设计到迭代的全过程:用审查经验提炼检查清单,封装成 course-review/ 目录,又通过「双 Agent」模式反复打磨指令。但这个 Skill 还只是 一组 Markdown 文件——Agent 怎么发现它、加载它、在真实任务中调用它?
4.1 在 AgentScope 中加载 Skill
好消息是,你不需要自己写完整的加载逻辑。AgentScope Java 用 ClasspathSkillRepository 解析 SKILL.md,SkillBox 注册 Skill 并生成 Agent 可见的提示。
Python 课件 vs AgentScope Java
| Python 课件 | AgentScope Java |
|---|---|
toolkit.register_agent_skill("course-review") |
skillBox.registerSkill(agentSkill) |
toolkit.get_agent_skill_prompt() |
skillBox.getSkillPrompt() |
| 按需读子文件 |
load_skill_through_path(框架内置)+ read_file
|
ReActAgent(..., toolkit=toolkit) |
ReActAgent.builder().skillBox(skillBox).toolkit(toolkit) |
准备工作:SKILL.md + YAML frontmatter
框架要求入口文件名为 SKILL.md(不是 README.md),并从 YAML frontmatter 提取 name、description。上篇已完成升级;course-review 入口示例:
---
name: course-review
description: |
审查课程内容的技术准确性、代码正确性和教学质量。当用户要求审查、审计或评估现有的课程或培训材料(含 Jupyter Notebook)时使用此技能。
owner: 教学设计团队
maintainers:
- course-quality-lead
version: 2.1.0
last-reviewed: 2026-01-15
---
# CourseReviewSkill
## 审查流程
1. 使用 extract_notebook_toc 提取目录
2. 按需加载 code-quality.md、content-accuracy.md 等
3. 调用 validate_notebook_dependencies 做结构化检查
4. 按模板输出审查报告
装配 SkillBox(只注册 course-review)
package com.baoma.ai.debug.support;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.memory.InMemoryMemory;
import io.agentscope.core.model.Model;
import io.agentscope.core.skill.AgentSkill;
import io.agentscope.core.skill.SkillBox;
import io.agentscope.core.skill.repository.ClasspathSkillRepository;
import io.agentscope.core.tool.Toolkit;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
public final class CourseReviewSkillSupport {
private CourseReviewSkillSupport() {
}
public static List<AgentSkill> loadSkillsFromClasspath(String skillsRoot) throws IOException {
try (ClasspathSkillRepository repo = new ClasspathSkillRepository(skillsRoot)) {
return repo.getAllSkills();
}
}
public static SkillBoxAndToolkit createSkillBox(Path workspace) throws IOException {
Toolkit toolkit = new Toolkit();
SkillBox skillBox = new SkillBox(toolkit);
AgentSkill courseReview = null;
for (AgentSkill skill : loadSkillsFromClasspath("skills")) {
if (!"course-review".equals(skill.getName())) {
continue;
}
skillBox.registerSkill(skill);
courseReview = skill;
}
if (courseReview != null) {
skillBox.registerSkill(courseReview);
// 注意:registration().tool() 每次赋值会覆盖前一个,不能链式挂两个 @Tool 类
toolkit.registerTool(new CourseReviewFileTools(workspace));
toolkit.registerTool(new CourseReviewValidateNotebookTool(workspace));
}
return new SkillBoxAndToolkit(skillBox, toolkit);
}
public static ReActAgent createSkillBoundReviewer(
String apiKey,
String modelName,
Toolkit toolkit,
SkillBox skillBox,
int maxIters) {
Model model = AgentScopeDashScopeModels.buildDashScopeChatModel(apiKey, modelName);
return ReActAgent.builder()
.name("CourseReviewer")
.sysPrompt("你是教程审查员。根据系统注入的 Skill 元数据与 load_skill 工具完成审查,输出结构化报告。")
.model(model)
.toolkit(toolkit)
.skillBox(skillBox)
.memory(new InMemoryMemory())
.maxIters(maxIters)
.build();
}
public record SkillBoxAndToolkit(SkillBox skillBox, Toolkit toolkit) {
}
}
具体路径:

第一步:注册 Skill 并查看生成的 Prompt
package com.baoma.ai.debug;
import com.baoma.ai.debug.support.CourseReviewSkillSupport;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.nio.file.Path;
class CourseReviewSkillPromptAgentScopeTest {
@Test
@DisplayName("SkillBox.getSkillPrompt 包含 course-review 的 name 与 description")
void skillPromptContainsMetadata() throws Exception {
var box = CourseReviewSkillSupport.createSkillBox(Path.of("."));
String prompt = box.skillBox().getSkillPrompt();
System.out.println("=== Skill Prompt(节选)===\n");
System.out.println(prompt.length() > 2000
? prompt.substring(0, 2000) + "\n...(truncated)"
: prompt);
Assertions.assertTrue(prompt.contains("course-review") || prompt.contains("course_review"));
Assertions.assertFalse(prompt.contains("反模式清单") && prompt.length() < 500,
"完整 SKILL body 不应全部塞进 prompt(渐进式披露)");
}
}
执行。
框架读取 frontmatter,生成类似结构(节选):
<available_skills>
<skill>
<name>course-review</name>
<description>审查课程内容的技术准确性……</description>
<owner>教学设计团队</owner>
<version>2.1.0</version>
</skill>
</available_skills>
提示告诉 Agent「这里有一个 Skill,要用时再读详情」——而不是把整个 SKILL.md 和所有子文件塞进 system prompt。
第二步:集成 ReActAgent 并执行审查
package com.baoma.ai.debug;
import com.baoma.ai.debug.support.CourseReviewScopeTestSupport;
import com.baoma.ai.debug.support.CourseReviewSkillSupport;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.message.Msg;
import io.agentscope.core.skill.SkillBox;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
class CourseReviewSkillBoxAgentScopeTest {
@TempDir
Path workspace;
@BeforeEach
void prepareWorkspace() throws Exception {
try (var in = getClass().getClassLoader()
.getResourceAsStream("agentscope-skill-demo/sample-course.ipynb")) {
Assertions.assertNotNull(in);
Files.copy(in, workspace.resolve("sample-course.ipynb"));
}
}
@Test
@DisplayName("AgentScope:SkillBox 加载 course-review 并审查 Notebook")
void skillBoxCourseReview() throws Exception {
String apiKey = CourseReviewScopeTestSupport.requireDashScopeApiKey();
String modelName = CourseReviewScopeTestSupport.reviewModelName();
CourseReviewScopeTestSupport.printApiKeyLoaded(apiKey);
var box = CourseReviewSkillSupport.createSkillBox(workspace);
SkillBox skillBox = box.skillBox();
System.out.println("=== Skill 注入 Prompt 预览(节选)===\n");
String skillPrompt = skillBox.getSkillPrompt();
System.out.println(skillPrompt.length() > 1200
? skillPrompt.substring(0, 1200) + "\n...(truncated)"
: skillPrompt);
ReActAgent agent = CourseReviewSkillSupport.createSkillBoundReviewer(
apiKey, modelName, box.toolkit(), skillBox, 28);
Msg user = Msg.builder()
.textContent("""
请审查工作区中的 sample-course.ipynb,评估能否发布。
使用 course-review 技能;按需加载子规则文件;输出结构化审查报告。
""")
.build();
System.out.println("\n[user]: " + user.getTextContent());
Msg reply = Objects.requireNonNull(agent.call(user).block(), "Agent 返回为空");
String text = Objects.requireNonNullElse(reply.getTextContent(), "");
System.out.println("\n=== 审查报告(SkillBox)===\n" + text);
Assertions.assertFalse(text.isBlank());
}
}
执行以上java
这次 Agent 不应再给出「整体不错,建议发布」的敷衍结论。它会按需加载 code-quality.md、content-accuracy.md、style-guide.md,并调用 validate_notebook_dependencies,点出 openpyxl 依赖、众数填充不一致、风格问题等。
三个设计决策
为什么只注入 description 而非全文?
上下文窗口是公共资源。框架只把 description(约数十 token)注入可见提示;工作流程、反模式清单等内容,由 Agent 决定使用 Skill 后再通过load_skill/read_file按需加载。为什么 Agent 需要文件读取工具?
Agent 先从 description 判断是否需要这个 Skill,再读SKILL.md获取详细指令,需要更细节时再读子文件——渐进式披露在框架层的落地。为什么 description 字段的质量关键?
当 Agent 装备了多个 Skill 时,它根据 description 判断当前任务应该使用哪一个。模糊描述(如「处理课程相关任务」)会导致匹配失败;具体描述(说明做什么、什么时候触发)才能确保正确激活。
4.2 让 Agent 自动生成 Skill
手动编写 Skill 是必要的学习过程。熟练之后,可以让 Agent 辅助完成初稿:本仓库提供 skill-creator 元 Skill(保留意图捕获、SKILL.md 规范、渐进式披露;去掉与 Java 环境无关的 CLI 链)。
元 Skill:skill-creator.md 的具体内容
---
name: skill-creator
description: 帮助用户创建新的 Agent Skill,包括需求分析、SKILL.md 编写和结构设计。当用户想要从零创建一个 Skill、改进现有 Skill 的结构或内容时使用。
---
# Skill Creator
一个帮助你创建高质量 Agent Skill 的工具。
创建 Skill 的核心流程:
1. 明确 Skill 的目标和触发场景
2. 收集需求细节和边界条件
3. 编写 SKILL.md 初稿
4. 创建测试用例验证效果
5. 根据反馈迭代改进
你的职责是判断用户当前处于哪个阶段,然后帮助他们推进。如果用户已经有了初稿,可以直接进入改进阶段。
---
## 创建 Skill
### Capture Intent(捕获意图)
首先理解用户的意图。当前对话中可能已经包含了用户想要封装的工作流(比如他们说"把这个流程变成一个 Skill")。如果是这样,先从对话历史中提取信息——使用了哪些工具、步骤顺序、用户做了哪些修正、观察到的输入输出格式。用户可能需要补充细节,确认后再进入下一步。
1. 这个 Skill 要让 Agent 完成什么任务?
2. 什么情况下应该触发这个 Skill?(哪些用户提问/场景)
3. 期望的输出格式是什么?
4. 是否需要设置测试用例来验证 Skill 的效果?有客观可验证输出的 Skill(文件转换、数据提取、代码生成、固定流程步骤)适合测试用例;主观输出的 Skill(写作风格、设计)通常不需要。根据 Skill 类型建议合适的默认方案,但让用户决定。
### Interview and Research(访谈与调研)
主动询问边界情况、输入输出格式、示例文件、成功标准和依赖关系。在搞清楚这些之前,不要急着写测试用例。
如果有可用的工具(搜索文档、查找类似 Skill、查阅最佳实践),先做调研,带着充分的上下文来减轻用户的负担。
### Write the SKILL.md(编写 SKILL.md)
基于用户访谈,填写以下组成部分:
- **name**:Skill 标识符
- **description**:触发条件和功能描述。这是主要的触发机制——既要说明 Skill 做什么,也要说明何时使用。所有"何时使用"的信息都放在这里,而不是放在正文中。注意:为了确保触发率,description 应该适度"积极"。例如,与其写"构建数据可视化面板",不如写"构建数据可视化面板。当用户提到仪表盘、数据展示、指标监控,或想要展示任何类型的数据时都应使用此 Skill,即使他们没有明确要求'面板'。"
- **compatibility**:所需工具、依赖(可选,很少需要)
- **正文内容**
---
## Skill 编写指南
### Skill 的结构
skill-name/
├── SKILL.md (必需)
│ ├── YAML frontmatter (name, description 必填)
│ └── Markdown 指令
└── Bundled Resources (可选)
├── scripts/ - 用于确定性/重复性任务的可执行代码
├── references/ - 按需加载到上下文中的文档
└── assets/ - 输出中使用的文件(模板、图标、字体)
### Progressive Disclosure(渐进式披露)
Skill 使用三级加载系统:
1. **元数据**(name + description)- 始终在上下文中(约 100 词)
2. **SKILL.md 正文** - Skill 触发时加载(理想情况 <500 行)
3. **捆绑资源** - 按需加载(不限大小,脚本可以在不加载的情况下执行)
这些字数是近似值,如果需要可以更长。
**关键模式:**
- 保持 SKILL.md 在 500 行以内;如果接近这个限制,添加层级结构并指明下一步应该阅读哪些文件
- 在 SKILL.md 中清晰引用文件,并说明何时应该读取它们
- 对于大型参考文件(>300 行),包含目录
**领域组织**:当 Skill 支持多个领域/框架时,按变体组织:
cloud-deploy/
├── SKILL.md (工作流 + 选择逻辑)
└── references/
├── aws.md
├── gcp.md
└── azure.md
Agent 只读取相关的参考文件。
### 安全原则
Skill 不得包含恶意软件、利用代码或任何可能危害系统安全的内容。Skill 的内容在被描述时不应让用户感到意外。不要创建误导性的 Skill 或旨在促进未授权访问的 Skill。
### 编写模式
在指令中优先使用祈使句。
**定义输出格式**——可以这样做:
markdown
## 报告结构
始终使用这个模板:
# [标题]
## 摘要
## 关键发现
## 建议
**示例模式**——包含示例很有用:
markdown
## Commit 消息格式
**示例 1:**
Input: Added user authentication with JWT tokens
Output: feat(auth): implement JWT-based authentication
### 编写风格
尽量向模型解释"为什么"事情很重要,而不是堆砌强硬的 MUST/NEVER。利用心智理论,让 Skill 通用化而非局限于特定示例。先写初稿,然后用新鲜的眼光审视并改进。
---
## 迭代改进
### 如何思考改进方向
1. **从反馈中泛化。** 我们创建的 Skill 将被在各种不同的场景中使用。不要针对测试用例过度拟合,而是理解用户反馈背后的通用模式。与其添加僵硬的限制条件,不如尝试不同的表达方式或工作模式。
2. **保持指令精简。** 移除没有发挥作用的内容。如果 Agent 在执行 Skill 时浪费时间在无用的步骤上,考虑删除导致这种行为的指令部分。
3. **解释为什么。** 尽量解释每条指令背后的原因。当前的大模型很聪明,给出好的上下文后能超越机械执行。如果你发现自己在写 ALWAYS 或 NEVER(全大写),这是一个信号——重新表述,解释推理过程,让模型理解为什么这很重要。
4. **寻找重复工作。** 如果多个测试用例中 Agent 都独立编写了类似的辅助脚本,这强烈暗示 Skill 应该捆绑该脚本。写一次,放在 `scripts/` 中,让 Skill 指引 Agent 使用它。
---
## 测试用例
编写 Skill 初稿后,构思 2-3 个真实的测试提示——真正的用户会实际说的话。与用户分享:"这是我想测试的几个用例,看起来合理吗?需要补充吗?"
好的测试用例是具体的、有细节的,而不是抽象的请求。
不好:`"格式化这个数据"`, `"从 PDF 中提取文本"`
好:`"我老板刚发了一个 xlsx 文件给我(在我的下载目录里,叫'Q4销售数据_最终版v2.xlsx'),她让我加一列利润率百分比。营收在 C 列,成本在 D 列。"
write_file 工具(与 read_file 对称)
package com.baoma.ai.debug.support;
import io.agentscope.core.tool.Tool;
import io.agentscope.core.tool.ToolParam;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class CourseReviewWriteFileTools {
private final Path workspaceRoot;
public CourseReviewWriteFileTools(Path workspaceRoot) {
this.workspaceRoot = workspaceRoot;
}
@Tool(name = "write_file", description = "将内容写入工作区相对路径;目录不存在则自动创建")
public String writeFile(
@ToolParam(name = "path", description = "相对路径,如 skills/api-doc-review/SKILL.md")
String path,
@ToolParam(name = "content", description = "文件全文")
String content) throws IOException {
Path target = workspaceRoot.resolve(path).normalize();
if (!target.startsWith(workspaceRoot.normalize())) {
return "错误:路径越界";
}
if (target.getParent() != null) {
Files.createDirectories(target.getParent());
}
Files.writeString(target, content);
return "已写入 " + path;
}
}
装配 skill-creator + 创建 Agent
package com.baoma.ai.debug.support;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.memory.InMemoryMemory;
import io.agentscope.core.model.Model;
import io.agentscope.core.skill.AgentSkill;
import io.agentscope.core.skill.SkillBox;
import io.agentscope.core.skill.repository.ClasspathSkillRepository;
import io.agentscope.core.tool.Toolkit;
import java.io.IOException;
import java.nio.file.Path;
public final class SkillCreatorSupport {
private SkillCreatorSupport() {
}
public static SkillBoxAndToolkit createCreatorSkillBox(Path workspace) throws IOException {
Toolkit toolkit = new Toolkit();
SkillBox skillBox = new SkillBox(toolkit);
AgentSkill creator = null;
try (ClasspathSkillRepository repo = new ClasspathSkillRepository("skills")) {
for (AgentSkill skill : repo.getAllSkills()) {
if ("skill-creator".equals(skill.getName())) {
creator = skill;
skillBox.registerSkill(skill);
}
}
}
if (creator != null) {
skillBox.registerSkill(creator);
toolkit.registerTool(new CourseReviewFileTools(workspace));
toolkit.registerTool(new CourseReviewWriteFileTools(workspace));
}
return new SkillBoxAndToolkit(skillBox, toolkit);
}
public static ReActAgent createSkillCreatorAgent(
String apiKey,
String modelName,
Toolkit toolkit,
SkillBox skillBox) {
Model model = AgentScopeDashScopeModels.buildDashScopeChatModel(apiKey, modelName);
return ReActAgent.builder()
.name("SkillCreator")
.sysPrompt("你是一个 Agent Skill 设计师。遵循 skill-creator 技能指引生成符合规范的 Skill 目录。")
.model(model)
.toolkit(toolkit)
.skillBox(skillBox)
.memory(new InMemoryMemory())
.maxIters(16)
.build();
}
public record SkillBoxAndToolkit(SkillBox skillBox, Toolkit toolkit) {
}
}
加载 skill-creator 并生成新 Skill
package com.baoma.ai.debug;
import com.baoma.ai.debug.support.CourseReviewScopeTestSupport;
import com.baoma.ai.debug.support.SkillCreatorSupport;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.message.Msg;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
class SkillCreatorAgentScopeTest {
@TempDir
Path workspace;
@Test
@DisplayName("AgentScope:skill-creator 生成 api-doc-review Skill 目录")
void generateApiDocReviewSkill() throws Exception {
String apiKey = CourseReviewScopeTestSupport.requireDashScopeApiKey();
String modelName = CourseReviewScopeTestSupport.reviewModelName();
CourseReviewScopeTestSupport.printApiKeyLoaded(apiKey);
var box = SkillCreatorSupport.createCreatorSkillBox(workspace);
ReActAgent creator = SkillCreatorSupport.createSkillCreatorAgent(
apiKey, modelName, box.toolkit(), box.skillBox());
Msg user = Msg.builder()
.textContent("""
帮我创建一个审核 REST API 文档的 Skill,目录名 api-doc-review。
要检查:1. 所有端点是否有示例请求和响应;
2. 错误码是否有说明;3. 参数是否有类型标注。
请用 write_file 写入 skills/api-doc-review/SKILL.md 及必要的子文件。
""")
.build();
System.out.println("[user]: " + user.getTextContent());
Msg reply = Objects.requireNonNull(creator.call(user).block());
System.out.println("\n=== Skill Creator 回复 ===\n" + reply.getTextContent());
Path skillMd = workspace.resolve("skills/api-doc-review/SKILL.md");
if (Files.isRegularFile(skillMd)) {
System.out.println("\n=== 已生成 SKILL.md ===\n" + Files.readString(skillMd));
Assertions.assertTrue(Files.readString(skillMd).contains("api-doc-review")
|| Files.readString(skillMd).contains("API"));
} else {
System.out.println("\n[提示] 模型可能未调用 write_file;请检查回复或重跑。");
Assertions.assertFalse(Objects.requireNonNullElse(reply.getTextContent(), "").isBlank());
}
}
}
执行上面的最终方法。
Agent 可能生成完整的 api-doc-review/ 目录;也可能只回复计划而未调用 write_file——可多跑几次。
这是加速,不是替代。 你仍需理解上篇五步编写法,才能判断 Agent 生成的内容是否合理。专家用 skill-creator 快速产出高质量 Skill;不理解设计原则的新手,即使有元 Skill 的创建的skill也很难产出可用结果。
小贴士:生成的 Skill 初稿一定要配合上篇 「双 Agent」评测 + 本篇中的skill的规范。自动生成只解决「写出来」;评测解决「写得对不对」。
4.3 Skill 市场:复用社区的专家知识
前两节介绍了注册和生成自建 Skill。在更广的 Coding Agent 生态中,社区已形成 Skill 共享平台。
发现 Skill
# 按关键词搜索
npx skills find typescript
# 输出示例:
# Install with npx skills add <owner/repo@skill>
# vercel-labs/agent-skills@typescript-best-practices
不带参数运行 npx skills find 可进入交互式模糊搜索。找到感兴趣的 Skill 后,在平台详情页查看完整内容与安全审计评级。
安装和使用
npx skills add vercel-labs/agent-skills --skill vercel-react-best-practices
npx skills update
安装后,Skill 就是一组 Markdown 文件,与上篇结构一致。Java 侧将目录放入工程的 skills/ 资源,用 ClasspathSkillRepository(开发)或 FileSystemSkillRepository(部署)加载。
社区 Skill 与自建 Skill 的分工
| 场景 | 推荐方式 |
|---|---|
| 通用技术最佳实践(React 性能、代码规范) | 社区 Skill |
| 成熟的行业标准流程(安全审计、SEO) | 社区 Skill |
| 企业内部业务流程和质量标准 | 自建 Skill |
| 团队特有的代码规范和架构约定 | 自建 Skill |
| 领域专家的隐性知识(如课程审核) | 自建 Skill |
使用时的注意事项
- 避免 Skill 过多:过多 Skill 会撑满 system prompt,稀释核心指令
- 注意版本匹配:依赖的工具或框架更新后,Skill 内命令可能过时
- 合理预期:Skill 提升的是下限(少犯低级错误),不是无限抬高上限
4.4 本节回顾
本节围绕 Skill 的「用起来」展开三个层次:
| 层次 | 要点 |
|---|---|
| 注册与加载(§4.1) |
SkillBox + getSkillPrompt 只注入 description;Agent 按需读详细指令——渐进式披露在框架层的落地 |
| 自动生成(§4.2) | skill-creator 元 Skill + write_file 生成初稿;仍需评测验证 |
| 社区复用(§4.3) |
npx skills 发现/安装;与自建 Skill 互补 |
5 当 Skill 遇上团队
5.1 一个人的成功,十个人的混乱
你在 §4 中成功构建了 course-review,把课程审核时间从 2 小时缩短到约 20 分钟。主管很满意,让你把这个 Skill 推广给整个教学设计团队——10 个人。你把 SKILL.md 发到群里,附了一段简短的使用说明。
一个月后,问题接踵而至。
版本混乱:你根据最新的课程标准更新了评分权重,但有 3 位同事还在用旧版 Skill。同一门课程被审出矛盾的结论——你的报告说「结构合理」,同事的报告说「章节划分不当」。团队不得不花半天时间排查,才发现是 Skill 版本不一致。
误修改:实习生觉得 SKILL.md 中「避免使用 print() 进行调试」这条规则太严格,自行改成了「允许在开发阶段使用 print()」。结果 Agent 开始把所有 print() 调试代码标记为「符合规范」,三周后才被发现。
知识断档:你休假两周,团队遇到 3 个审核边界问题——「代码示例中的注释算不算教学内容?」「引用外部 API 文档需不需要检查链接有效性?」——没人能回答,因为这些判断逻辑只存在于你的经验中,没有写进 Skill。
这三个问题有一个共同点:CourseReviewSkill 作为一个人的作品运转良好,但它没有为团队协作而设计。
5.2 朴素修补:为什么「群里发一下」行不通
面对这些问题,你可能会想到几个直觉性的修补方案。
统一存放位置 — 把 Skill 放到共享网盘。但谁有权修改?如果 10 个人都能改,就等于没人负责。
发一份使用说明文档 — 文档和 Skill 分离,两周后 Skill 迭代了,文档还停留在旧版本。
由你一个人负责所有修改 — 你成了瓶颈,且你不是所有审核领域的专家。
这些修补失败的根本原因在于:Skill 不是普通配置文件,而是封装了专家知识的可执行资产。管理它需要明确的所有权和工程化的生命周期。
换一个角度:Skill 是能力放大器——资深设计师一周审 10 门课,封装成 Skill 后,10 名初级成员都能调用。但要让放大器在团队中稳定运转,需要解决 所有权、协作流程、质量保障。

5.3 领域团队所有权:让最懂业务的人管 Skill
谁最有能力定义和维护 course-review?教学设计团队——他们最了解「什么样的课程才算好」。
新的分工模式
| 角色 | 职责 |
|---|---|
| 专家 (Expert) | 定义标准、封装 Skill、审核变更 |
| 团队成员 (Member) | 调用 Skill、反馈问题、提交改进建议 |
专家从「亲自执行每一次审核」转变为「维护审核标准」,影响力从线性增长变为可复制的规模化。
不同领域的 Skill 由对应业务团队负责
| 领域 | 所有权团队 | 维护的 Skill 示例 |
|---|---|---|
| 课程质量 | 教学设计团队 | CourseReviewSkill |
| 数据分析 | 数据团队 | DataCleaningSkill |
| 安全合规 | 安全团队 | CodeSecurityScanSkill |
业务专家 + 技术人员协作
| 角色 | 职责 |
|---|---|
| 业务专家 (SOP Owner) | 制定流程标准与边界条件 |
| 技术人员 (Agent Architect) | Skill 结构调优与工具挂载 |
协作流程分三步:
- 1、业务专家用自然语言描述"一个优秀的审核员会怎么做"
- 2、技术人员将其转化为结构化的 Skill 格式
- 3、双方一起测试、迭代、根据 Agent 实际产出调整指令
为了在 Skill 文件中明确所有权信息,可以在 YAML frontmatter 中添加管理元数据:
在 frontmatter 中明确所有权
---
name: course-review
description: |
审核课程内容的质量和结构完整性。当用户要求检查、审核或评估课程材料时触发。
owner: 教学设计团队
maintainers:
- zhang-wei
- li-na
version: 2.1.0
last-reviewed: 2026-01-15
---
这些字段让任何人打开文件就能知道:有问题找谁、当前是什么版本、上次审查是什么时候。
小贴士:GitHub 可配置 CODEOWNERS,例如
/skills/course-review/ @teaching-design-team,对该路径的修改自动要求教学设计团队审批。
/skills/course-review/ @org/course-quality
/skills/skill-creator/ @org/agent-platform
5.4 Skills-as-Code:用工程方法管理 Skill 资产
所有权明确后,下一个问题是:团队如何协作迭代 Skill?答案是把 Skill 当作代码来管理——version control、code review、CI/CD。这些软件工程的成熟实践同样适用于 Skill。
Skills-as-Code 生命周期

| 阶段 | 说明 |
|---|---|
| 1、本地迭代 | 维护者在本地修改 Skill,用几个代表性案例快速验证效果 |
| 2、PR / Review | 提交 Pull Request,由团队其他成员(尤其是业务专家)审查变更 |
| 3、CI 评测 | 自动化流水线运行评测用例,确保修改没有引入回归问题 |
| 4、部署上线 | 合并到主分支后,所有使用该 Skill 的 Agent 自动获取最新版本 |
| 5、反馈收集 | 跟踪使用情况,驱动改进 |
将 classpath 中的 skills 部署到磁盘(模拟 CI 构建拷贝)
package com.baoma.ai.debug.support;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
public final class SkillsAsCodeDeploySupport {
private SkillsAsCodeDeploySupport() {
}
public static Path deployClasspathSkillsTo(Path targetSkillsRoot) throws IOException {
Files.createDirectories(targetSkillsRoot);
ClassLoader cl = SkillsAsCodeDeploySupport.class.getClassLoader();
deploySkillDir(cl, targetSkillsRoot, "course-review");
deploySkillDir(cl, targetSkillsRoot, "skill-creator");
return targetSkillsRoot;
}
private static void deploySkillDir(ClassLoader cl, Path targetRoot, String skillName) throws IOException {
Path skillDir = targetRoot.resolve(skillName);
Files.createDirectories(skillDir);
String prefix = "skills/" + skillName + "/";
copyResource(cl, prefix + "SKILL.md", skillDir.resolve("SKILL.md"));
copyIfExists(cl, prefix + "code-quality.md", skillDir.resolve("code-quality.md"));
copyIfExists(cl, prefix + "content-accuracy.md", skillDir.resolve("content-accuracy.md"));
copyIfExists(cl, prefix + "style-guide.md", skillDir.resolve("style-guide.md"));
copyIfExists(cl, prefix + "outdated-api.md", skillDir.resolve("outdated-api.md"));
}
private static void copyResource(ClassLoader cl, String cp, Path dest) throws IOException {
try (InputStream in = cl.getResourceAsStream(cp)) {
if (in == null) {
throw new IOException("classpath 资源不存在: " + cp);
}
Files.copy(in, dest, StandardCopyOption.REPLACE_EXISTING);
}
}
private static void copyIfExists(ClassLoader cl, String cp, Path dest) throws IOException {
try (InputStream in = cl.getResourceAsStream(cp)) {
if (in != null) {
Files.copy(in, dest, StandardCopyOption.REPLACE_EXISTING);
}
}
}
public static void copyDirectoryFromClasspath(Path targetSkillsRoot) throws IOException {
deployClasspathSkillsTo(targetSkillsRoot);
}
}
从磁盘加载:FileSystemSkillRepository
package com.baoma.ai.debug;
import com.baoma.ai.debug.support.SkillsAsCodeDeploySupport;
import io.agentscope.core.skill.AgentSkill;
import io.agentscope.core.skill.repository.FileSystemSkillRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;
class FileSystemSkillRepositoryAgentScopeTest {
@TempDir
Path skillsRoot;
@Test
@DisplayName("FileSystemSkillRepository 从磁盘加载 course-review")
void loadSkillFromFilesystem() throws Exception {
SkillsAsCodeDeploySupport.deployClasspathSkillsTo(skillsRoot);
try (FileSystemSkillRepository repo = new FileSystemSkillRepository(skillsRoot)) {
Assertions.assertTrue(repo.skillExists("course-review"));
AgentSkill skill = repo.getSkill("course-review");
Assertions.assertEquals("course-review", skill.getName());
Assertions.assertTrue(skill.getDescription().contains("审查"));
Assertions.assertTrue(skill.getSkillContent().contains("审查流程"));
System.out.println("Repository source: " + repo.getSource());
System.out.println("Skill version metadata: " + skill.getMetadataValue("version"));
}
}
}
frontmatter CI 校验器
package com.baoma.ai.debug.support;
import io.agentscope.core.skill.util.MarkdownSkillParser;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
public final class SkillFrontmatterValidator {
private static final Set<String> REQUIRED_FIELDS = Set.of(
"name", "description", "owner", "version", "last-reviewed"
);
private SkillFrontmatterValidator() {
}
public static List<String> validateSkillFile(Path skillMdPath) throws IOException {
List<String> errors = new ArrayList<>();
if (!Files.isRegularFile(skillMdPath)) {
errors.add("文件不存在: " + skillMdPath);
return errors;
}
String content = Files.readString(skillMdPath);
var parsed = MarkdownSkillParser.parse(content);
Map<String, Object> meta = parsed.getMetadata();
for (String field : REQUIRED_FIELDS) {
if (!meta.containsKey(field) || meta.get(field) == null
|| meta.get(field).toString().isBlank()) {
errors.add("缺少必需字段: " + field);
}
}
if (parsed.getContent() == null || parsed.getContent().isBlank()) {
errors.add("Markdown body 为空");
}
return errors;
}
}
单测:PR 阶段 frontmatter 门禁
package com.baoma.ai.debug;
import com.baoma.ai.debug.support.SkillFrontmatterValidator;
import com.baoma.ai.debug.support.SkillsAsCodeDeploySupport;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;
import java.util.List;
class SkillFrontmatterValidationTest {
@TempDir
Path skillsRoot;
@Test
@DisplayName("course-review SKILL.md 通过 frontmatter 校验")
void courseReviewSkillPassesValidation() throws Exception {
SkillsAsCodeDeploySupport.deployClasspathSkillsTo(skillsRoot);
Path skillMd = skillsRoot.resolve("course-review/SKILL.md");
List<String> errors = SkillFrontmatterValidator.validateSkillFile(skillMd);
System.out.println("校验结果: " + (errors.isEmpty() ? "通过" : errors));
Assertions.assertTrue(errors.isEmpty(), () -> String.join("; ", errors));
}
}
GitHub Actions 示例(Maven 评测)
name: validate-skills
on:
pull_request:
paths:
- 'module-ai/src/test/resources/skills/**'
- 'skills/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Check SKILL.md metadata
run: mvn -pl module-ai test -Dtest=SkillFrontmatterValidationTest
- name: Run skill evaluation
env:
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
run: mvn -pl module-ai test -Dtest=CourseReviewSkillBoxAgentScopeTest
废弃与清理(建议每季度)
- 过去 3 个月未被调用 → 考虑归档
- 对应的业务流程已取消 → 立即删除
- 与其他 Skill 功能重叠 → 合并精简
核心度量
| 指标 | 定义 | 意义 |
|---|---|---|
| 复用率 | Skill 被不同成员调用的频次 | 只有被反复调用的 Skill 才是有效资产 |
| 交付标准一致性 | 新手使用 Skill 后的产出 vs 专家基线 | 衡量 Skill 是否真正传递了专家能力 |
5.5 落地路径:从种子团队到全面推广
理解了所有权和工程化管理之后,推荐 「种子 Skill」 策略,不要一步全员铺开:
| 步骤 | 动作 |
|---|---|
| 1 | 选 3~5 人先锋队 |
| 2 | 聚焦一个高价值痛点(课程审核、Code Review、数据清洗) |
| 3 | 跑通闭环:识别专家知识 → 封装 Skill → 评测验证 → 团队试用 |
| 4 | 用数据说服其余团队(审核时间缩短 X%、质量达专家水平 Y%) |
4 周落地清单
| 周次 | 动作 | 产出 |
|---|---|---|
| W1 | 课程审查场景 |
course-review + 至少 1 个 @Tool
|
| W2 | frontmatter + CODEOWNERS | frontmatter 单测进 CI |
| W3 | 2 个黄金 Notebook 样例 | SkillBox 审查回归门禁 |
| W4 | skill-creator 第二个 Skill |
api-doc-review 初稿 + 人审 + 评测 |
治理演进
| 维度 | 试点期 | 推广期 |
|---|---|---|
| 谁写 | 技术骨干 + 业务专家 | 任何成员可提交 |
| 谁审 | 同行 Code Review | Skill 审核组 / CODEOWNERS |
| 谁批准 | 团队负责人 | 领域负责人 |
引入 Skill 初期(1~3 个月)效率可能不升反降——学习规范、调试 Agent、建评测都需要时间。让管理层有预期,用试点数据建立信心。
责任归属:Agent 调用 Skill 产出经人合并上线时,Merge 即负责——点合并的人对结果承担最终责任。
小结回顾:
1、推广困境:个人构建的 Skill 在团队中使用时,会遇到版本混乱、误修改和知识断档三类问题.
2、能力放大器:Skill 的核心价值是将专家能力规模化复制,但需要所有权和工程化流程来保障
3、领域团队所有权:最有度量能力的团队拥有并维护对应领域的 Skill,通过 YAML 元数据和 CODEOWNERS 明确权责。
4、Skills-as-Code:将 Skill 当作代码管理,走 PR → Review → CI 评测 → 部署的完整工程生命周期。
5、落地策略:从种子团队开始,聚焦高价值痛点,用数据说话,逐步推广到全组织。