06 - Skills 系统:从 SKILL.md 到动态 Prompt 注入
学习目标
理解 CowAgent 的 Skills 系统如何管理双层目录(builtin + custom)、启用/禁用配置、环境要求过滤与动态 Prompt 注入。
前置知识
技能设计的通用原理见 技能设计。本章聚焦 CowAgent 的具体实现。
项目实践
双层技能目录
CowAgent 的技能加载支持两个来源:
| 来源 | 路径 | 说明 |
|---|---|---|
| 内置技能 | 项目 skills/ 目录 | 随项目发布的默认技能(skill-creator, image-generation, knowledge-wiki) |
| 自定义技能 | workspace skills/ 目录(默认 ~/cow/skills/) | 用户安装或创建的技能 |
启动时 _sync_builtin_skills() 将内置技能从项目目录同步到 workspace,确保用户可在不修改项目代码的情况下拥有全部默认技能。
技能生命周期
技能过滤
技能的启用状态由 skills_config.json 管理,支持:
- 启用/禁用:用户通过 Web 控制台或 CLI 切换
- 环境变量要求:部分技能依赖特定 API Key(如
BOCHA_API_KEYfor web_search) - 知识系统开关:
conf().get("knowledge", True)控制 knowledge-wiki 技能是否启用
# 伪代码 — 技能过滤核心逻辑def filter_skills(self, skill_filter=None, include_disabled=False): entries = list(self.skills.values())
# 1. 环境变量要求检查 entries = [e for e in entries if should_include_skill(e, self.config)]
# 2. 名称过滤(用户指定的 skill_filter) if skill_filter: entries = [e for e in entries if e.skill.name in skill_filter]
# 3. 启用状态过滤 if not include_disabled: entries = [e for e in entries if self.is_skill_enabled(e.skill.name)]
# 4. 知识系统开关 if not conf().get("knowledge", True): entries = [e for e in entries if e.skill.name != "knowledge-wiki"]
return entriesPrompt 注入策略
PromptBuilder 中的技能注入策略:
- 扫描可用技能:对每个技能检查
description是否与用户需求匹配 - 匹配优先:有匹配技能时,提示 LLM 使用
read工具读取 SKILL.md - 不可用技能提示:显示安装指引,而非静默跳过
## 🧩 技能系统(mandatory)
在回复之前:扫描下方 <available_skills> 中每个技能的 <description>。- 如果有技能匹配:使用 `read` 工具读取 SKILL.md,然后遵循其中指令- 如果没有技能适用:不要读取任何 SKILL.md,直接使用通用工具
**重要**: 技能不是工具,不能直接调用。使用技能的唯一方式是用 `read` 读取 SKILL.md 文件。技能热刷新
Agent.get_full_system_prompt() 每次构建时调用 skill_manager.refresh_skills(),确保磁盘上的技能变更立即生效,无需重启 Agent。
问题与规避
| 问题 | CowAgent 的规避策略 |
|---|---|
| 技能目录被修改但未刷新 | refresh_skills() 每次构建 prompt 时调用 |
| 技能环境变量缺失导致报错 | should_include_skill() 检查环境变量,不满足时不注入 prompt |
| 内置技能更新覆盖用户自定义 | 同步时先 rmtree(dst) 再 copytree(src, dst),用户自定义技能应在 workspace 而非项目目录编辑 |
| LLM 一次读取多个技能 | prompt 约束”永远不要一次性读取多个技能” |
设计取舍
为什么技能不是工具,而是通过 read 文件来使用?
这种设计(“Skills as files” 模式)使得技能定义完全独立于代码——一个 SKILL.md 文件就是完整的技能定义,可以用自然语言编写,无需编程。相比”注册制”技能(需要代码注册到框架),“文件制”技能更灵活、更易扩展。代价是每次使用需要一次额外的 read 工具调用。