【无标题】

手撕 Agent Skills:用 TypeScript 从零实现一个最小 Skill 运行时

📖 摘要 :2026 年 9 月,Agent Skills 取代 MCP 成为 GitHub Trending 的新霸主------Matt Pocock 的 skills 仓库单日涨星 24.7 万,Anthropic 也放出了官方规范。但绝大多数文章还停留在「SKILL.md 怎么写」。本文换一个工程视角:用 TypeScript 从零实现一个最小 Skills 运行时,覆盖「发现 → 解析 → 触发匹配 → 执行 → 校验」完整链路,并附带可直接 npx tsx 跑起来的 demo,帮你真正看懂 Skill 是怎么被「加载」和「调用」的。

🏷️ 关键词:Agent Skills,LLM Agent,TypeScript,SKILL.md,AI 工程化

目录

  • 一、背景与痛点
  • 二、核心原理
    • [2.1 什么是 Agent Skill](#2.1 什么是 Agent Skill)
    • [2.2 Skill 运行时到底要干哪些活](#2.2 Skill 运行时到底要干哪些活)
    • [2.3 一次 Skill 调用发生了什么](#2.3 一次 Skill 调用发生了什么)
  • 三、实战:从零实现最小运行时
    • [3.1 项目结构](#3.1 项目结构)
    • [3.2 SKILL.md 格式约定](#3.2 SKILL.md 格式约定)
    • [3.3 解析器:把 Markdown 读成 Skill 对象](#3.3 解析器:把 Markdown 读成 Skill 对象)
    • [3.4 触发器匹配:怎么知道该用哪个 Skill](#3.4 触发器匹配:怎么知道该用哪个 Skill)
    • [3.5 执行器:把步骤真正跑起来](#3.5 执行器:把步骤真正跑起来)
    • [3.6 跑通 demo](#3.6 跑通 demo)
  • 四、踩坑与优化
    • [4.1 前端 matter 解析的坑](#4.1 前端 matter 解析的坑)
    • [4.2 触发器怎么写才不误召回](#4.2 触发器怎么写才不误召回)
    • [4.3 安全红线:千万别 eval 外部 SKILL.md](#4.3 安全红线:千万别 eval 外部 SKILL.md)
    • [4.4 上下文压缩与成功标准](#4.4 上下文压缩与成功标准)
  • 五、总结

一、背景与痛点

如果你用过 Claude Code、Codex CLI 这类编码 Agent,大概率见过 SKILL.md 这个文件。2025 年 Anthropic 把 Agent Skills 标准化为一套基于 Markdown 的规范,到 2026 年 9 月它彻底爆发:个人开发者把自己私有的 .agents 目录开源出来,官方也下场定标准,社区开始抢生态位。

为什么大家都在卷 Skills?因为两个真实痛点:

  1. 上下文窗口会被耗尽。长任务里,Agent 把历史对话全部塞回 prompt,token 飞速上涨、成本飙升、还容易「忘了之前定过什么」。把流程沉淀成 Skill,Agent 只在需要时加载对应那一份短文档,而不是每次都背一整本操作手册。
  2. Agent 容易「提前宣布胜利」。没有明确的成功标准,模型经常「我觉得改好了」就结束。Skill 里写死执行步骤 + 校验项,等于给 Agent 套上一个 checklist,逼它逐项对账。

所以 Skill 的本质不是「知识库」,而是把专家的「过程性知识」(该怎么做、做到什么程度算完)编码成 Agent 可随时按需加载的协议。它和 MCP 是互补的两层:MCP 解决「Agent 能调用什么工具接口」,Skills 解决「Agent 该按什么流程用这些工具」。

二、核心原理

2.1 什么是 Agent Skill

一个 Skill 通常就是一个 SKILL.md,由三段组成:

  • 声明区(frontmatter)name / description / triggers(触发条件)。运行时靠它做匹配。
  • 步骤区:把「怎么做」写成有序步骤,可绑定到真实代码 handler。
  • 校验区(可选):成功标准,让 Agent 知道「做到什么程度算完」。

注意一个关键区别:frontmatter 里的是声明式知识(要做什么) ,步骤里的是过程式知识(怎么做),这两者要拆开,这是高质量 Skill 的共识写法。

2.2 Skill 运行时到底要干哪些活

所谓「运行时」,就是这段代码要做的事:

职责 说明
发现(Discovery) 扫描某个目录,找到所有 *.md
解析(Parse) 把 Markdown 的 frontmatter 读成结构化 Skill 对象
匹配(Match) 用户一句自然语言进来,判断该用哪个 Skill
执行(Execute) 回放步骤,或调用绑定的真实函数
校验(Verify) 对照成功标准判断是否完成(demo 中简化为打印)

2.3 一次 Skill 调用发生了什么

复制代码
用户 query
   │
   ▼
[Match] 遍历所有 Skill 的 triggers,做关键词打分
   │ 命中 score 最高的那个
   ▼
[Execute] 打印 steps,若存在绑定 handler 则调用真实代码
   │
   ▼
[Verify] 对照成功标准确认(demo 中打印结果即视为完成)

三、实战:从零实现最小运行时

3.1 项目结构

复制代码
agent-skills-demo/
├── skill-runtime.ts     # 最小运行时(解析+匹配+执行)
├── skills/
│   ├── date-formatter.md
│   ├── code-review.md
│   └── security-check.md
└── package.json

3.2 SKILL.md 格式约定

我们用一个极简子集,只依赖 frontmatter,无需任何第三方 YAML 库:

markdown 复制代码
---
name: date-formatter
description: 将用户输入格式化为 ISO8601 标准时间字符串
triggers:
  - 格式化
  - 格式化时间
  - 当前时间
  - format date
steps:
  - 解析用户输入的时间表达式,缺省用当前时间
  - 校验解析结果,无效则明确报错
  - 输出 ISO8601 字符串
---

3.3 解析器:把 Markdown 读成 Skill 对象

typescript 复制代码
import * as fs from 'fs';
import * as path from 'path';

interface Skill {
  name: string;
  description: string;
  triggers: string[];
  steps: string[];
  filePath: string;
}

// 解析 SKILL.md 的 frontmatter,返回 Skill 对象
function parseSkillMarkdown(raw: string): Skill | null {
  const fmMatch = raw.match(/^---\n([\s\S]*?)\n---/);
  if (!fmMatch) return null;
  const fm = fmMatch[1];

  const get = (key: string): string | undefined => {
    const m = fm.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'));
    return m ? m[1].trim() : undefined;
  };

  // 解析 YAML 列表(triggers / steps),只认 "- xxx" 形式
  const parseList = (key: string): string[] => {
    const out: string[] = [];
    let capture = false;
    for (const line of fm.split('\n')) {
      if (new RegExp(`^${key}:\\s*$`).test(line)) { capture = true; continue; }
      if (capture) {
        const item = line.match(/^\s*-\s+(.+)$/);
        if (item) out.push(item[1].trim());
        else if (/^[\w]+:/.test(line)) capture = false; // 遇到下一个 key 停止
      }
    }
    return out;
  };

  const name = get('name');
  if (!name) return null;
  return {
    name,
    description: get('description') ?? '',
    triggers: parseList('triggers'),
    steps: parseList('steps'),
    filePath: '',
  };
}

// 扫描目录,收集所有 Skill
function loadSkills(dir: string): Skill[] {
  if (!fs.existsSync(dir)) return [];
  const skills: Skill[] = [];
  for (const file of fs.readdirSync(dir)) {
    if (!file.endsWith('.md')) continue;
    const fp = path.join(dir, file);
    const skill = parseSkillMarkdown(fs.readFileSync(fp, 'utf-8'));
    if (skill) { skill.filePath = fp; skills.push(skill); }
  }
  return skills;
}

3.4 触发器匹配:怎么知道该用哪个 Skill

匹配不用大模型,用最朴素的关键词打分就能跑通,而且快、零成本:

typescript 复制代码
function matchSkill(skills: Skill[], query: string): Skill | null {
  const q = query.toLowerCase();
  let best: Skill | null = null;
  let bestScore = 0;
  for (const s of skills) {
    let score = 0;
    for (const t of s.triggers) {
      const tl = t.toLowerCase();
      if (q.includes(tl)) score += 2;            // 整句命中,权重高
      for (const w of tl.split(/[\s,,、]+/).filter(Boolean)) {
        if (w.length > 1 && q.includes(w)) score += 1; // 词级重叠,权重低
      }
    }
    if (score > bestScore) { bestScore = score; best = s; }
  }
  return best;
}

工程实践里,复杂场景会把这一步换成 embedding 向量检索(语义匹配),但「关键词打分兜底」永远值得保留------它是零依赖、可解释的保底逻辑。

3.5 执行器:把步骤真正跑起来

步骤可以是纯文本说明,也可以绑定真实代码。我们用一个 handlers 注册表把 Skill 名映射到函数,实现「过程性知识 → 真实执行」的闭环:

typescript 复制代码
type Handler = (input: string) => string;
const handlers: Record<string, Handler> = {
  'date-formatter': (input) => {
    // 从自然语言里抽出日期片段,抽不到则默认当前时间
    const m = input.match(/\d{4}[-/]\d{1,2}[-/]\d{1,2}([ T]\d{1,2}:\d{2}(:\d{2})?)?/);
    const d = m ? new Date(m[0]) : new Date();
    if (isNaN(d.getTime())) return '⚠️ 无法解析日期: ' + input;
    return d.toISOString();
  },
};

function executeSkill(skill: Skill, input: string): string {
  console.log(`\n▶ 命中 Skill: ${skill.name}`);
  console.log(`  描述: ${skill.description}`);
  console.log('  执行步骤:');
  skill.steps.forEach((s, i) => console.log(`   ${i + 1}. ${s}`));
  const handler = handlers[skill.name];
  if (handler) {
    const result = handler(input);
    console.log(`  执行结果: ${result}`);
    return result;
  }
  return '(该 Skill 仅含步骤说明,无绑定代码)';
}

3.6 跑通 demo

typescript 复制代码
function main() {
  const skillsDir = path.join(__dirname, 'skills');
  const skills = loadSkills(skillsDir);
  console.log(`已加载 ${skills.length} 个 Skill`);

  const queries = [
    '帮我格式化一下 2026-09-07 08:30:00',
    'review 一下这段代码有没有问题',
    '做一次安全检查,看看有没有注入风险',
  ];
  for (const q of queries) {
    const s = matchSkill(skills, q);
    if (s) executeSkill(s, q);
    else console.log(`\n· 未匹配到 Skill: ${q}`);
  }
}
main();

package.json 只需:

json 复制代码
{ "name": "agent-skills-demo", "type": "commonjs" }

运行:

bash 复制代码
cd agent-skills-demo
npx tsx skill-runtime.ts

你会看到 3 句 query 分别命中 date-formattercode-reviewsecurity-check 三个 Skill,其中 date-formatter 还真的输出了 ISO8601 时间字符串。从零到可运行,不到 120 行代码。

四、踩坑与优化

4.1 前端 matter 解析的坑

--- 在 Markdown 正文里也常见(如分割线),正则务必用 ^---\n 锚定文件头,否则会把正文误判成 frontmatter。生产环境建议直接用 js-yaml + 成熟的 frontmatter 库,不要自己造轮子解析复杂 YAML。

4.2 触发器怎么写才不误召回

  • 写「意图」不写「关键词堆砌」帮我格式化时间时间, date, format 更容易被真实 query 命中。
  • 用同义词兜底 :中文场景加拼音/英文别名(格式化时间 + format date)。
  • 避免过于宽泛的词检查 这种词会让一半 Skill 都被误召回。

4.3 安全红线:千万别 eval 外部 SKILL.md

Skill 文件很可能是动态加载甚至远程拉取的。永远不要把 SKILL.md 内容直接 eval / new Function 执行 。正确做法是:Skill 只描述「意图和步骤」,真实能力由你代码里白名单注册的 handlers 提供。这样即使有人塞进恶意 Skill,最多只是多打印几行步骤,动不了你的系统。

4.4 上下文压缩与成功标准

前面提到的「上下文压缩」是高阶玩法:Skill 执行完把长过程压成一份结构化状态文件(如 state.json),下次会话只加载这份摘要,而不是重读全部历史。再给每个 Skill 配一个 success_criteria 字段,执行器末尾对照检查------这是抑制「提前宣布胜利」最有效的一招。

五、总结

Agent Skills 之所以在 2026 年 9 月屠榜,是因为它把「模型能力趋同之后,真正的护城河在过程性知识」这件事变成了一套可复用、可分发、可标准化的协议。会写 Skill 的人,本质是在把自己的工程经验「封装成可增值的资产」

今天我们手撕的最小运行时,已经跑通了「发现 → 解析 → 匹配 → 执行」四步。要上生产,你只需要补三件事:

  1. 匹配升级为 embedding 向量检索 + 关键词兜底
  2. 加载支持远程/动态 Skill 仓库,并加签名校验;
  3. 执行接入真实工具(也就是和 MCP 打通:Skills 决定流程,MCP 提供工具)。

当你把团队里那些「只有老人懂的踩坑流程」都写成 Skill,新人接入、Agent 调用、跨项目复用,全都一次性解决。这,就是 Skills 经济的入口。


相关推荐
源码学社16 天前
股市复盘Skills全流程资料包:从手工复盘到AI赋能的进阶指南
人工智能·skills·股市复盘·股市复盘skills
Esaka_Forever16 天前
Prompt vs Skill 对比解读
skills
ZGi.ai22 天前
如何用 ZGI 打造企业级 AI 智能体
aiagent·企业ai·skills·智能体工作流·zgi
浅安的邂逅23 天前
WorkBuddy 配置免费Agnes 模型-免费生成图片、视频
人工智能·skills·workbuddy·agnes 模型免费生图、视频
小小工匠24 天前
Skill - 把风格写成规格:拆解 ian-xiaohei-illustrations 中文配图 Skill
画图·skills
ze_sir1231 个月前
Impeccable 下载安装及使用
claude code·skills·impeccable
johnny2331 个月前
Skills生态项目:AAS、MiniMax Office、OpenSkills、Skill Graphs、SkillNet
skills
潜龙95271 个月前
自动化技能合集 (Automation Skills)
自动化·skills
神奇霸王龙1 个月前
MCP v5 Agent Skills 屠夫榜:5 旗舰子代理
网络·人工智能·ai·aigc·agent·mcp·skills