Skills · 可复用的 prompt 包,还能按文件门控

Skills 是 grok 复用 prompt 的一等公民:SKILL.md(frontmatter + 正文)。这一章看它的作用域优先级、防御式 frontmatter 解析、从不改 system prompt 的几条注入路径,以及一个精巧设计——glob 门控的技能,直到你碰到匹配文件才现身。

作者 David更新于 第 34 篇(共 60 篇)

Skills · 可复用的 prompt 包,还能按文件门控

Skills 是把「反复用的一段指令/清单/流程」固化下来的单元——一个 SKILL.md(YAML frontmatter + Markdown 正文)。grok 的 skills 分在三处:xai-grok-agent/prompt/skills.rs 负责启动时的发现编排,xai-grok-tools/.../skills/ 放解析、作用域类型和格式化,会话中的门控与列表在 skill_discovery_tracker/。

作用域:一个 Ord 定优先级

SkillScope 是个 #[repr(u8)] 枚举,序数小 = 优先级高:Local(0,cwd/.grok/skills)> Repo(1)> User(2,~/.grok/skills)> Server(3)> Bundled(4,内置)> Plugin(5)。优先级用一个 sort_by_key(|s| s.scope) + 首见去重就实现了——把优先级编码成类型的 Ord,简单又不易错。

去重不是一刀切:跨作用域同名是有意的遮蔽;同一作用域里重名(比如复制了技能目录却忘了改 name)则两个都留,后来者改用目录名作身份,frontmatter 的 name 只作显示名。内置技能现在放在 ~/.grok/bundled/skills/,按 Bundled 作用域发现;旧版本解压进 ~/.grok/skills/ 的副本会遮住它们,所以每次启动都清理一遍——只删与已发布内容逐字节一致的,你改过的保留。

和 AGENTS.md 一样,项目里的技能也要过目录信任:未受信时整条项目链(连同 workspace-user 覆盖层)都不扫;插件技能也只取已启用且受信的插件。

防御式解析:一个坏字段不该丢掉整个技能

frontmatter 解析(parse_skill_frontmatter)做了三层容错:① 先按无类型 HashMap 逐字段解;② 失败就把带 YAML 特殊字符的值加引号重试;③ 再不行就逐行抢救 name/description/when-to-use 这几个关键字段。设计意图很明确:模型或用户写的 SKILL.md frontmatter 常有小毛病,一个坏字段不该让整个技能消失。

注入:一个信封,三条路

技能的「信封」是一对 XML 标签,见 skill.rs 里的 build_skill_message(第 42–47 行):

pub fn build_skill_message(skill: &SkillInfo, content: &str) -> String {
    format!(
        "<skill name=\"{}\" description=\"{}\" path=\"{}\">\n{}\n</skill>",
        skill.name, skill.description, skill.path, content
    )
}

标签给模型一个清晰的身份与边界:里面是要遵循的额外指令,不是被调用的程序。但它对属性和正文都不做转义(有测试专门钉住这一点),所以这是语义边界,不是防注入的安全边界。

它的注释仍自称「所有路径共用的格式化出口」,可在 HEAD 里真正调用它的只剩一处。技能正文实际有三条路进上下文:

  • 模型调 skill 工具:正文作为工具结果返回,用的是另一种信封 <skill_content name="…">,附技能目录和文件清单。模型也常直接用 read_file 读 SKILL.md——技能列表里每一项都给了绝对路径。
  • 用户敲 /技能名 参数:拼 prompt 时就地展开成 <skill name="…" args="…">,外面包一层 <skill_information>,追加在 <user_query> 之后,省掉一次工具往返。
  • agent 定义里的 skills: 预加载:这才是 build_skill_message 的唯一调用方,拼好后接在该 agent 的提示词正文前面。

一个和 system prompt 呼应的设计:技能列表和激活通告都作为 <system-reminder> 用户消息注入,SkillUpdateEffects 的注释写明「system prompt 从不因技能而改动」。唯一的例外是第三条:预加载的技能本就属于那个 agent 自己的提示词。

glob 门控:碰到文件才现身

最精巧的是 paths: 门控(skill_discovery_tracker/conditional.rs)。一个技能若声明了 paths:(比如 ["**/*.proto"]),它默认被藏起来(is_pending),不进技能列表——直到模型碰到匹配的文件(读/改一个 .proto),SkillDiscoveryReminder 在那次工具调用后 activate_for_paths 把它激活现身。匹配用 gitignore 风格,对还没创建的文件也管用。

算「碰到」的只有 read_file、list_dir、search_replace 和 apply_patch(多文件补丁里每个文件都算),bash 和 grep 碰过的路径不算。还有个限制:concise 模式下这个 reminder 整体关闭,paths: 技能也就不会被激活。

这个门控解决一个真实痛点:一个大项目可能有几十个专用技能(protobuf 规范、前端组件约定……),全塞进列表既费 token 又干扰模型。按「你正在碰的文件」按需现身,让技能列表始终小而相关。激活是会话级的,/clear 会重新收起。这是「上下文是稀缺资源」这条原则在技能系统里的又一次体现。

和《从 LLM 到 Coding Agent》对照

对照你熟悉的 Claude Code 的 Skills:grok 的实现几乎是它的源码版——作用域优先级、SKILL.md frontmatter、<skill> 注入、glob 门控。教学最小实现里这些都不存在;它们是「让一个 agent 可被大量复用 prompt 扩展、还不撑爆上下文」的工程。下一章看更重的扩展:MCP。


上一篇:AGENTS.md 项目规则 · 下一篇:MCP 客户端

本页目录