技能

SKILL.md 从哪些目录被发现、同名时谁赢、内置技能怎样打包进产物,技能目录怎样按预算进入系统提示词,以及 skill 工具与斜杠命令两条调用路径。

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

技能是一个目录加一份 SKILL.md。模型平时只看到由名字和描述组成的一份目录,放在系统提示词末尾;需要时用 skill 工具把某个技能的全文读进上下文,再照着做。用户也可以在 TUI 里输入 /名字 直接点名。

实现分在三层:扫描与同名裁决在 packages/agent-modules/skills(包名 @mavis/skills),根目录、内置技能、目录渲染与读取在第一代运行时的 packages/local-runtime/src/skills/,提示词拼装、斜杠提醒和插件技能在 local-runtime-v2。

怎么用

  • 看清单:/skills [filter] 打开只读面板,分“Built-in”“User”“Other”三组列出技能、来源及是否可用(packages/tui/src/tui/features/inspection/capabilities.ts:15)。
  • 点名调用:每个启用的技能都会变成一条斜杠命令 /<名字> [instructions],补全列表里的说明带 [Skill] 前缀(packages/tui/src/tui/controller/run/active-run-flow.ts:82)。
  • 初始化项目:mcode init(可带目录参数,默认当前目录)就是在该目录打开 TUI 并提交 /init,由内置的 init 技能生成根目录的 AGENTS.md(packages/tui/src/cli/program.ts:84)。
  • 放自己的技能:个人技能放 <数据目录>/skills/<名字>/SKILL.md,只给某个 Agent 用的放 <数据目录>/agents/<Agent>/skills/,项目技能放仓库里的 .minimax/skills/<名字>/SKILL.md,内置的 skill-creator 也是这样引导的(packages/local-runtime/assets/skills/skill-creator/SKILL.md:62);Claude Code、Codex 等工具的技能目录也会被扫描,见下表。数据目录默认是 ~/.minimax-code,可用 MINIMAX_DATA_DIR 或 MAVIS_DATA_DIR 改(packages/tui/src/runtime/data-dir.ts:20)。
  • 调整外部来源:<数据目录>/config.yaml 里 skills 下的 external 段有 enabled(总开关)、walkUp(是否逐级向上找到 Git 根)、duplicateWarn,以及按来源的 sources.<来源>.enabled 与 priority(packages/config/src/skills-config.ts:47)。
  • 改了就生效:技能根目录与各技能目录都挂了文件监听,变更后防抖 200 毫秒、最长等 1 秒就重新扫描(packages/agent-modules/skills/src/registry.ts:311)。

TUI 的 /skills 面板不能启用或禁用技能,命令表里也没有技能安装命令。第一代运行时里有从技能市场与 GitHub 安装技能的代码(packages/local-runtime/src/skills/hub-api.ts:113),命令行里用不上。

发现:十一个来源与同名裁决

readConfiguredSkillRoots 列出全部根目录(packages/local-runtime/src/skills/roots.ts:22),外部来源的默认优先级来自 DEFAULT_SKILLS_CONFIG(skills-config.ts:62):

来源路径种类优先级
workspace-minimax工作区的 .minimax/skillsworkspace65
workspace-cc工作区的 .claude/skillsworkspace60
workspace-agents工作区的 .agents/skillsworkspace55
user-cc~/.claude/skillsuser40
user-codex~/.codex/skillsuser35
user-agents~/.agents/skillsuser30
Agent 目录<数据目录>/agents/<Agent>/skillsagent100,兼容的旧 Agent 名为 90
用户全局<数据目录>/skillsglobal未设,按 0 计
内置落盘<数据目录>/.builtin-skillsbuiltin20
内置 Agent 专属内置 Agent 资源目录下当前 Agent 的 skills 子目录builtin10
内置全局产物里的 assets/skillsbuiltin未设,按 0 计

工作区的三个目录从当前目录开始扫描;walkUp 打开时(默认)逐级向上,直到遇到含 .git 的目录(roots.ts:140),离当前目录越近的层级优先级加成越大,每层差千分之一(roots.ts:83)。

同名时先比种类,再比优先级(registry.ts:1042)。种类的先后是 project、workspace、agent、global 与 user 并列、最后 builtin(registry.ts:72):

const SOURCE_RANK: Record<SkillSourceKind, number> = {
  project: 0,
  workspace: 1,
  agent: 2,
  global: 3,
  user: 3,
  builtin: 4,
};

project 这一档在根目录列表里没有用到,实际排第一的是工作区目录,内置技能永远排在最后;Agent 目录的优先级数字虽然最大,也排在工作区之后。一个不太直观的结果:<数据目录>/skills 与 ~/.claude/skills 同属第 3 档,前者没有设优先级按 0 计,后者是 40,两处有同名技能时胜出的是 ~/.claude/skills 那一份。落败的条目不会消失,会连同“被哪个来源遮蔽”的原因留在快照里,供诊断查看(registry.ts:1033)。

扫描只看每个根目录下一层的 <目录>/SKILL.md(registry.ts:528),嵌在技能内部的 SKILL.md(例如 lark-tools 里那一批)不算独立技能。SKILL.md 本身不许是符号链接;技能目录可以是符号链接,但只有 agent、global、user、builtin 四种根允许它指向根目录之外(registry.ts:474),工作区根不行。

SKILL.md 的格式

独立技能的格式很宽松(registry.ts:678):名字取 frontmatter 的 name,没有就用目录名;标题取 title 或正文第一个标题;描述取 description 或正文第一段。没有 frontmatter、YAML 写错、缺描述都只记诊断,不影响加载。frontmatter 里起作用的键:

键作用
name、description、title名字、目录里的描述、标题
displayNames、descriptions按语言区分的显示名与描述,如 zh-Hans(registry.ts:914)
requires_beta 或 requiresBeta只有 config.yaml 的 beta 里对应开关为真才可见(packages/local-runtime/src/skills/registry-family.ts:171)
listed写成 false 就不进目录(registry-family.ts:201)

正文里的 {{DATA_DIR}} 会替换成当前数据目录(registry.ts:64)。一个完整的例子是内置的 resume-codex(packages/local-runtime/assets/skills/resume-codex/SKILL.md:1):

---
name: resume-codex
description:
  Safely continue the task from a Codex CLI or VS Code session in the current workspace. Use when
  invoked as /resume-codex with an optional Codex session id, or when the user asks to pick up their
  latest Codex work.
descriptions:
  zh-Hans: '安全接续当前工作区中的 Codex CLI 或 VS Code session。'
displayNames:
  zh-Hans: '接续 Codex Session'
---

插件带来的技能由 v2 的插件系统解析,规则更严:文件必须以 YAML frontmatter 开头,必须有 description,名字缺省时退回目录名(packages/local-runtime-v2/src/service/plugin-system/skill/reader.ts:18);开了 agentSkills 选项时名字必须显式声明,还要检查 license、allowed-tools 为字符串,compatibility 长 1 到 500 个字符,metadata 只能是字符串到字符串的映射(reader.ts:43)。

内置技能:打包、落盘与清单

内置技能的源文件有两处:packages/local-runtime/assets/skills/ 下 18 个目录,以及主 Agent 专属的 packages/local-runtime-v2/assets/agents/mavis/skills/ 下 2 个。pnpm build 把前者复制到产物目录 dist 下的 assets,把 v2 的 Agent 资源树复制到其中的 agents(scripts/build.mjs:113),同时按名单排除 6 个技能(scripts/lib/builtin-skills.mjs:1):create-agent、mavis、mavis-doctor、mavis-team、miniapp-creator、plugin-creator。这 6 个在公开仓库里本来就没有目录。

运行时启动时,再把打包的内置技能复制到 <数据目录>/.builtin-skills。是否需要重写由一个组合指纹决定,指纹不变就一次也不写;一旦重写,这个目录会被整个清空,注释明说放进去的用户文件也会被删掉(packages/local-runtime/src/skills/seed-builtin.ts:44)。想改内置技能,应该在优先级更高的目录放一份同名技能,而不是直接改这里。

在命令行里各技能能不能用,取决于 resolveFeatureAwareBuiltinSkillNames 的门控(packages/local-runtime/src/agent/feature-owned-skills.ts:11):普通内置技能要求当前 Agent 有它依赖的工具,归属某项能力的技能跟着那项能力开关。

技能用途命令行里的条件
init生成根目录 AGENTS.md需要 read、write、bash
code-review按用户指定的范围审查代码缺陷需要 read
deep-research分五步做需要外部核实的深度研究需要 read、write、todowrite,外加联网搜索、web_fetch、bash 之一
docx、pdf、pptx、xlsx读写办公文档需要 read、write、bash
skill-creator、skill-refiner新建技能、按证据修补已有技能前者需要 read、write、bash,后者需要 read、edit
deploy-website、edit-deployed-website部署静态网站、修改已部署的网站需要 website_deploy 等工具
lark-tools通过官方 lark-cli 使用飞书需要 bash
llm-call直接调用已配置的模型需要 bash
mcode-tools-mastermcode-tools 命令行与连接器的总入口需要 bash,并且 requiresBeta: mcodeTools
visual-page生成可视化 HTML 页面需要 write
x-link-reader读取 X 或 Twitter 链接需要 web_fetch;中国区关闭(packages/local-runtime/src/skills/builtin.ts:15)
resume-codex接续 Codex CLI 或 VS Code 会话只在 CLI 能力档打开,且需要 bash
cu-desktopComputer Use 桌面操作指南需要 CU 模式,命令行关闭
minimax-code-product回答关于产品本身的问题需要 features.mavis,命令行关闭
control-in-app-browser控制内置浏览器由浏览器服务单独提供(packages/local-runtime-v2/src/service/browser-use/browser-skill-asset.ts:13),见浏览器工具

resume-codex 的开关只出现在 CLI 能力档里(packages/local-runtime/src/api/hosted-agent-capability-factory.ts:39),从代码看,这是命令行有而桌面宿主没有的内置技能。同一处还把 mavis-doctor、plugin-creator 列为禁用;这两个名字在内置技能 id 表里(packages/config/src/agent-capabilities.ts:50),但公开仓库里没有它们的目录。

进提示词:<available_skills> 与预算

每个回合组装系统提示词时,v2 先取可见技能,把插件技能作为附加条目并进来,再让第一代运行时渲染目录(packages/local-runtime-v2/src/service/turn-system/agent-host/preparation/config/local-agent-config-builder.ts:522)。目录是系统提示词的最后一层,排在记忆之后(local-agent-config-builder.ts:752)。

进目录之前还要过几道筛子:禁用的技能、listed 为 false 的技能、requires_beta 未开的技能、Agent 配置里没选中的技能都去掉;名字形如 mcp-<服务器名> 且对应 MCP 服务器已配置的技能也去掉(registry-family.ts:181)。渲染格式很简单(packages/local-runtime/src/skills/catalog.ts:347):

function renderCatalog(
  skills: readonly PreparedSkill[],
  descriptionLengths: readonly number[],
  notice?: string,
): string {
  const entries = skills.map((skill, index) => {
    const description = skill.description.slice(0, descriptionLengths[index] ?? 0).join('');
    return description ? `- ${skill.name}: ${description}` : `- ${skill.name}`;
  });
  return `<available_skills>\n${[...(notice ? [notice] : []), ...entries].join('\n')}\n</available_skills>`;
}

目录有预算:模型上下文窗口的 2%,封顶 5000 token,拿不到窗口大小时直接用 5000(catalog.ts:41)。相关常量与两句提示语在文件开头(catalog.ts:3):

export const LOCAL_SKILL_CATALOG_MAX_BUDGET_TOKENS = 5_000;
export const LOCAL_SKILL_DESCRIPTION_MAX_CODE_POINTS = 1_024;
const PROTECTED_SKILL_NAMES = new Set(['mcode-tools-master', 'minimax-code-product']);

const PARTIAL_DESCRIPTION_NOTICE =
  'Skill descriptions were shortened to fit the catalog budget.';
const PARTIAL_SKILL_NOTICE =
  'Some skills were omitted because not every skill name fits within the skills context budget.';

每条描述先压成一行、截到 1024 个码点,受保护的两个技能除外。整份目录放不下时按顺序让步:

  1. 受保护技能排在最前,描述保持完整。
  2. 其余描述统一缩短:先给内置技能分配长度,内置技能的描述全部放完整了,非内置技能才分得到;放得下第一句提示语就把它加在目录开头。
  3. 连只列名字都放不下时,从排序靠前的开始保留,其余整条丢掉,放得下就附上第二句提示语;留下的非受保护技能也只剩名字。

除受保护技能外,目录按名字排序,所以超预算时被丢掉的是字母序靠后的技能。

packages/agent-extension/src/skills.ts 里还有一个 skillsExtension,打算把目录渲染搬到 Agent 扩展接口上(packages/agent-extension/src/skills.ts:95),用的是 @mavis/skills 按字符计的预算,默认 20000 字符(registry.ts:81)。代码里找不到它的调用方,头注释提到的 packages/local-runtime/src/tools/skill-tool.ts 在仓库里也不存在(skills.ts:21);实际生效的是上面按 token 计的这一套。

调用:skill 工具与斜杠命令

模型这一侧只有一个 skill 工具,参数只有 name(packages/agent-tools/src/desktop/builtin-defs.ts:268)。工具说明要求:用户点名(包括 /名字)或目录里的名字与描述明显匹配时才加载;名字要与目录一字不差,插件技能保留前面的插件名前缀;加载时它必须是该步唯一的工具调用,读完全文再按说明做(builtin-defs.ts:275)。

执行时先按名字精确查找独立技能,查不到再到插件技能里按规范化后的名字找(packages/local-runtime/src/api/local-native-tools.ts:132)。找到后返回整份文件(含 frontmatter),前面加两行头(packages/agent-tools/src/desktop/local-skill.ts:50):

function formatSkillContent(name: string, content: string, location?: string): string {
  const header = [`# Skill: ${name}`];
  if (location) header.push(`Location: ${location}`);
  return `${header.join('\n')}\n\n${content}`;
}

Location 用的是以 files: 开头的地址(registry.ts:1066),技能目录里的附属文件要由模型按这个位置自己去读。找不到时返回以 Local skill not found: 开头的一行文本,工具说明要求模型这时不许声称用过该技能。

用户这一侧,TUI 用 /skills 同一份清单生成斜杠命令:跳过已禁用的、与内置命令或别名重名的、含空白或斜杠的、超过 128 个字符的名字(active-run-flow.ts:82)。选中技能命令后,TUI 并不自己读 SKILL.md,而是把 /名字 参数 原样作为用户消息提交。v2 在准备这条输入时识别开头的斜杠名(允许带一个冒号分隔的前缀),对上当前回合配置里的技能名,就在消息前面加一段提醒(packages/local-runtime-v2/src/service/turn-system/agent-host/assembly/local-turn-input-preparation.ts:831):

function buildSlashSkillReminder(
  canonical: AgentHostCanonicalUserInput,
  agentConfig: Readonly<Record<string, unknown>>,
): string {
  const requested = canonical.messages.reduce<string | undefined>((found, message) => {
    if (found) return found;
    return /^\/([A-Za-z0-9][A-Za-z0-9._-]*(?::[A-Za-z0-9][A-Za-z0-9._-]*)?)(?:\s|$)/u.exec(
      message.text.trimStart(),
    )?.[1];
  }, undefined);
  if (!requested) return '';
  const skills = Array.isArray(agentConfig.skills) ? agentConfig.skills : [];
  const match = skills.find((skill) => {
    const name = readString(readRecord(skill), 'name');
    return name?.toLowerCase() === requested.toLowerCase();
  });
  const name = readString(readRecord(match), 'name');
  return name
    ? [
        '<system-reminder>',
        `The user explicitly requested the \`${name}\` skill. Load it with the \`skill\` tool and follow it.`,
        '</system-reminder>',
      ].join('\n')
    : '';
}

这段提醒拼在用户消息正文之前(local-turn-input-preparation.ts:361)。技能正文依旧由模型调用 skill 工具读取,不会被直接塞进消息。匹配不分大小写,只认消息开头的第一个斜杠名。

启用状态与插件技能

独立技能可以按文件位置单独禁用。禁用集合由 v2 保存在偏好表里,键为 standalone-skill-disabled-state(packages/local-runtime-v2/src/service/plugin-system/skill/enabled-state.ts:8),再注入第一代的发现引擎;被禁用的技能既不进目录,也不能被 skill 工具读取。

插件技能的运行时名字是 插件名:技能名(packages/shared/src/plugin-skill-name.ts:9)。/skills 与斜杠命令用的清单里,独立技能在前,插件技能按名字排好附在后面,与独立技能同名的插件技能被跳过(packages/local-runtime-v2/src/application/session/runtime-skill-application.ts:154)。插件怎样安装、启用,它的技能怎样进入回合能力快照,见插件系统。

下一篇:插件系统——插件包的格式、官方与本地来源,以及它怎样把技能、MCP 服务器与钩子带进回合。

本页目录