Skills

五路来源与优先级、SKILL.md 与 frontmatter 解析、参数展开时的 XML 转义、Skill 工具与 /skill 斜杠命令,以及「能力即 Markdown」的实现路径。

作者 David更新于 第 19 篇(共 33 篇)

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——另一条扩展路径。

本页目录