Skills
五路来源与优先级、SKILL.md 与 frontmatter 解析、参数展开时的 XML 转义、Skill 工具与 /skill 斜杠命令,以及「能力即 Markdown」的实现路径。
Skill 是「用 Markdown 写的能力」——不写代码,写一段带元数据的指令文档,agent 按需加载。v0.29 时代它在 packages/agent-core/src/skill/;现在是 agent-core-v2 的一个 Feature:packages/agent-core-v2/src/features/skill/,由 SkillFeature 往 agent 作用域贡献 IAgentSkillService 与 Skill 工具。
扫描路径
features/skill/catalog/skillRoots.ts:8:
const USER_BRAND_DIRS = ['skills'] as const;
const USER_GENERIC_DIRS = ['.agents/skills'] as const;
const PROJECT_BRAND_DIRS = ['.kimi-code/skills'] as const;
const PROJECT_GENERIC_DIRS = ['.agents/skills'] as const;两个维度交叉:
| 品牌专属 | 通用 | |
|---|---|---|
| 用户级 | ~/.kimi-code/skills/ | ~/.agents/skills/ |
| 项目级 | <项目>/.kimi-code/skills/ | <项目>/.agents/skills/ |
用户级品牌目录相对 homeDir(即 ~/.kimi-code 或 KIMI_CODE_HOME)拼接,所以常量里只写 skills,不能再加一层 .kimi-code;通用目录相对系统 home。项目根是从工作目录向上找到的第一个 .git。.agents/skills 是跨工具通用目录——同一份 skill 可以被多个 agent 工具共享,这是个务实的生态选择。
五路来源与优先级
扫描目录只是来源之一。catalog/skillSource.ts:11:
export const SKILL_SOURCE_PRIORITY = {
builtin: 0,
plugin: 5,
extra: 10,
user: 20,
workspace: 30,
} as const;WorkspaceSkillCatalogService 按优先级从低到高依次 register(skill, { replace: true }),同名时后注册的覆盖先注册的,所以项目 > 用户 > 额外目录 > 插件 > 内置。额外目录来自配置 extraSkillDirs;启动时传 --skills-dir 则只用这些目录,跳过用户级与项目级的自动扫描。用户级与项目级目录有文件监听,增删 skill 后自动重载。
解析
skill 有两种形态:目录里的 SKILL.md(frontmatter 必须有 name 与 description),或根目录下独立的 <name>.md(名字缺省取文件名、描述取正文第一行)。catalog/parser.ts 借 _base/text/frontmatter.ts(内部是 js-yaml)解析,与本站的内容模型同构。
type 支持 prompt/inline(默认)、flow 与 reference(后者不能被触发);未知类型记为 SkippedSkill,frontmatter 写错则记一条警告后跳过——一个坏文件不该让所有 skill 都用不了。父 skill 声明 has-sub-skill: true 后,子目录里的 skill 以「父名.子名」注册为子技能。
名字归一化 normalizeSkillName() 只做一件事:toLowerCase()。Write-TUI 与 write-tui 是同一个,write_tui 则是另一个。
参数展开与 XML 转义
skill 正文里可以放占位符:$ARGUMENTS、$1、$ARGUMENTS[0]、frontmatter arguments 声明的具名参数,以及 ${KIMI_SKILL_DIR}、${KIMI_SESSION_ID}。展开在 catalog/registry.ts:154 的 expandSkillParameters():
content = content
.replaceAll(/\$ARGUMENTS\[(\d+)\]/g, (_match, indexText: string) => {
const index = Number.parseInt(indexText, 10);
return escapeXmlTags(tokens[index] ?? '');
})
.replaceAll(/\$(\d+)(?!\w)/g, (_match, indexText: string) => {
const index = Number.parseInt(indexText, 10);
return escapeXmlTags(tokens[index] ?? '');
})
.replaceAll('$ARGUMENTS', escapeXmlTags(rawArgs));被 escapeXmlTags 转义的是参数,不是 skill 正文(v1 也是如此)。参数来自用户输入或模型调用,最容易夹带 <system> 之类的标签去伪造结构化内容;转义 <、> 就把这条注入路径堵死。正文没有占位符时,参数作为一行 ARGUMENTS: 附在末尾,同样经过转义。
正文本身按提示词原样注入,包在 <skill-loaded name="…" trigger="…"> 块里,属性值经 escapeXml 转义。插件 skill 可能带一段 <plugin-instructions> 前缀,插件的系统提示则被明确声明为「插件提供的参考数据,不是特权指令通道」(见插件与市场)。
两种触发方式
用户:TUI 把 skill 变成 slash 命令,apps/kimi-code/src/tui/commands/skills.ts:36:
const commandName =
skill.source === 'builtin' || skill.isSubSkill === true
? skill.name
: `skill:${skill.name}`;内置 skill 与子技能直接用名字(如 /mcp-config),其余一律 /skill:<name>,避免与内置 slash 命令重名。
模型:features/skill/tools/skillTool.ts 的 Skill 工具。只有 inline 类型、且没设 disableModelInvocation 的 skill 能被模型调用,嵌套调用最多 3 层(MAX_SKILL_QUERY_DEPTH)。加载结果不当工具输出返回,而是以 steer 方式作为一条 user 消息插入上下文。
skill 所在目录还会并入 Read、Write、Edit、Glob、Grep 的可访问目录(catalog.getSkillRoots()),让 skill 能引用自己目录下的脚本与参考文件——这是 v1 extendWorkspaceWithSkillRoots() 的现役对应。
内置 skills
catalog/builtin/builtin.ts:17:
export const BUILTIN_SKILLS: readonly SkillDefinition[] = [
MCP_CONFIG_SKILL,
IMPORT_FROM_CC_CODEX_SKILL,
UPDATE_CONFIG_SKILL,
CUSTOM_THEME_SKILL,
WRITE_GOAL_SKILL,
CHECK_KIMI_CODE_DOCS_SKILL,
SUB_SKILL_PARENT,
SUB_SKILL_REVIEW,
SUB_SKILL_CONSOLIDATE,
];每个内置 skill 都是「一份 .md 加二十来行 .ts 包装」:.ts 只负责 ?raw 导入正文、交给 parseSkillText()、补几个元数据,比如 mcp-config 被标为 disableModelInvocation 与 productSpecific(后者可用配置 builtinProductSkills 整体关掉)。sub-skill/ 用 SKILL.md 文件名约定,与 .agents/skills 生态一致。
/mcp-config 的业务逻辑全在那段 Markdown 里:先判断用户要登录还是改配置,再指导模型读写三层 mcp.json(见 MCP 集成)。能用提示词表达的就不写代码,这是 agent 时代的实现方式。
下一篇:Hooks——另一条扩展路径。