# 技能

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

- 作者：David（道雾轩）
- 专栏：MiniMax Code 源码解读（https://daiw.org/manual/minimax-code.md）
- 最后更新：2026-09-19
- 原文：https://daiw.org/manual/minimax-code/skills
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

技能是一个目录加一份 `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/skills` | workspace | 65 |
| `workspace-cc` | 工作区的 `.claude/skills` | workspace | 60 |
| `workspace-agents` | 工作区的 `.agents/skills` | workspace | 55 |
| `user-cc` | `~/.claude/skills` | user | 40 |
| `user-codex` | `~/.codex/skills` | user | 35 |
| `user-agents` | `~/.agents/skills` | user | 30 |
| Agent 目录 | `<数据目录>/agents/<Agent>/skills` | agent | 100，兼容的旧 Agent 名为 90 |
| 用户全局 | `<数据目录>/skills` | global | 未设，按 0 计 |
| 内置落盘 | `<数据目录>/.builtin-skills` | builtin | 20 |
| 内置 Agent 专属 | 内置 Agent 资源目录下当前 Agent 的 `skills` 子目录 | builtin | 10 |
| 内置全局 | 产物里的 `assets/skills` | builtin | 未设，按 0 计 |

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

同名时先比种类，再比优先级（`registry.ts:1042`）。种类的先后是 `project`、`workspace`、`agent`、`global` 与 `user` 并列、最后 `builtin`（`registry.ts:72`）：

```ts
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`）：

```text
---
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-master` | `mcode-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-desktop` | Computer Use 桌面操作指南 | 需要 CU 模式，命令行关闭 |
| `minimax-code-product` | 回答关于产品本身的问题 | 需要 `features.mavis`，命令行关闭 |
| `control-in-app-browser` | 控制内置浏览器 | 由浏览器服务单独提供（`packages/local-runtime-v2/src/service/browser-use/browser-skill-asset.ts:13`），见[浏览器工具](https://daiw.org/manual/minimax-code/browser-use) |

`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`）：

```ts
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`）：

```ts
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`）：

```ts
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`）：

```ts
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`）。插件怎样安装、启用，它的技能怎样进入回合能力快照，见[插件系统](https://daiw.org/manual/minimax-code/plugins)。

下一篇：[插件系统](https://daiw.org/manual/minimax-code/plugins)——插件包的格式、官方与本地来源，以及它怎样把技能、MCP 服务器与钩子带进回合。
