AI研究

github/skills下34个Agent skill一个都没加载、手动调/plancode报unknown skill,Skill发现机制到底卡在哪?

👤 为我痴狂 👁 9 阅读 ❤ 0 点赞 ➦ 0 分享 📅 2026-11-08
首页› AI› AI研究› 正文
github/skills下34个Agent skill一个都没加载、手动调/plancode报unknown skill,Skill发现机制到底卡在哪?

摘要:一次典型的 Agent Skill 失效现场往往表现为两个症状同时出现:仓库里明明躺着 34 个 skill 目录,运行时一个都没注册;手工执行 /plancode 却返回 unknown skill。这两个症状看似独立,实则同源——它们都指向 Skill 发现(discovery)链路上的同一个断点。本文以“发现链路四段模型”为主线,把目录约定、清单解析、注册表索引、运行时解析拆成可观测的四段,逐段给出判定信号、复现命令与修复路径,并结合开源生态中 Skill 规范的演进,讨论为什么这类问题在工程上高频复发,以及如何用“发现契约”把它变成可测试、可回归的工程资产。

目录

一、导语:两个症状,一个断点

先把现场还原清楚。一个名为 github/skills 的目录下,按约定放置了 34 个 skill 子目录,每个子目录里都有一个 SKILL.md。启动 Agent 运行时后,技能列表为空;在会话里手动输入 /plancode,返回 unknown skill。用户的第一反应通常是“加载器坏了”,但工程上更准确的说法是:发现链路上某一环没有把“存在”翻译成“可见”。

这两个症状之所以值得放在一起讨论,是因为它们共享同一条数据通路。技能列表为空,说明注册表里没有条目;unknown skill 说明运行时在注册表里查不到这个名字。前者是“注册表为空”,后者是“查表失败”,二者是同一根因在不同观测点的投影。本文评述:把两个症状合并成一个断点来定位,比分别排查要高效得多,因为一旦确认注册表为空,unknown skill 就不需要再单独解释。

需要强调的是,Skill 发现机制并不是某个厂商的私有实现,它背后有一套逐渐收敛的开放约定。Anthropic 在 2025 年 10 月发布的 Agent Skills 规范中,把 skill 定义为一个包含 SKILL.md 的目录,文件头部用 YAML frontmatter 声明 name 与 description 两个必填字段,正文则是给模型阅读的操作说明(来源:Anthropic Engineering Blog, “Equipping agents for the real world with Agent Skills”, 2025-10)。这一约定后来被多个运行时采纳,成为事实上的互操作基线。理解这一点,才能理解为什么“目录对了但没加载”往往不是玄学,而是约定与实现之间的缝隙。

二、背景与问题定义:Skill 到底是什么

2.1 从“提示词模板”到“可发现的能力单元”

在 Agent 工程的早期,所谓“技能”通常就是一段被硬编码进系统提示词的文本,或者一个被工具调用包装的函数。这种做法的扩展性很差:每增加一个能力,系统提示词就膨胀一截,模型的注意力被稀释,维护成本线性上升。Agent Skills 规范的核心贡献,是把技能从“提示词片段”提升为“可发现、可组合、可渐进加载的能力单元”。所谓渐进加载(progressive disclosure),是指运行时先只读取每个技能的元数据(name、description),在模型判断需要时才加载完整正文。这一设计直接决定了发现机制的地位:如果元数据读不出来,渐进加载就无从谈起。

本文评述:渐进加载是一个被低估的架构决策。它把“发现”从一次性动作变成了分阶段动作,第一阶段只要求极轻量的元数据解析。这既是性能优势,也是故障面——元数据解析比正文解析更严格,任何格式偏差都会在第一阶段被放大成“技能不存在”。

2.2 一个可操作的定义

为了让后续排查有共同语言,这里给出一个工程上可操作的定义:Skill 是一个满足“目录 + 清单 + 元数据”三要素的最小单元。目录提供位置,清单(SKILL.md)提供入口,元数据(frontmatter 中的 name/description)提供可索引的标识。三者缺一,运行时都无法把它注册进技能表。这个定义看似朴素,但它把“加载失败”拆成了三个可独立验证的命题,后续所有排查都建立在这三个命题之上。

判定口诀:目录在不在、清单读不读、元数据全不全。三问皆过而技能仍不可见,问题一定在注册表或运行时解析层。

三、发现链路四段模型:一条主线贯穿全文

本文提出的分析主线是“发现链路四段模型”。它把从磁盘到运行时的整个过程切成四段,每段有明确的输入、输出和失败模式。这条主线的价值在于:它让“技能没加载”这个笼统描述,变成四个可以分别观测、分别复现、分别修复的环节。

段位 输入 输出 典型失败信号
① 目录约定 文件系统路径 候选目录列表 扫描根错误、符号链接未跟随
② 清单解析 SKILL.md 文本 结构化元数据 YAML 解析异常被吞、字段缺失
③ 注册表索引 元数据集合 名字到技能的映射 重名覆盖、名字规范化不一致
④ 运行时解析 调用名 技能对象或错误 unknown skill、大小写不匹配

本文评述:四段模型的关键洞察是“失败信号的可观测性逐段下降”。第一段的失败在文件系统层面一目了然,第四段的失败却只表现为一句 unknown skill,几乎不携带任何上下文。因此排查必须自前向后、逐段证伪,而不是从最模糊的第四段开始猜。这与分布式系统排障中“先看最上游指标”的原则一致。

为了让这个模型可落地,下面给出一段最小化的诊断脚本思路。它不依赖具体运行时,只做四段的前置检查,输出每一段的通过与否。读者可以据此快速定位断点所在。

# 四段诊断骨架(伪代码,示意逻辑)
for dir in scan_roots:                    # ① 目录约定
    if not dir.exists(): report("ROOT_MISSING", dir); continue
    for skill_dir in dir.iterdir():
        md = skill_dir / "SKILL.md"
        if not md.exists(): report("MANIFEST_MISSING", skill_dir); continue
        meta = parse_frontmatter(md)      # ② 清单解析
        if meta is None: report("YAML_INVALID", md); continue
        if not meta.get("name") or not meta.get("description"):
            report("META_INCOMPLETE", md); continue
        registry.add(meta["name"], skill_dir)   # ③ 注册表索引
for call in calls:                        # ④ 运行时解析
    if call not in registry: report("UNKNOWN_SKILL", call)

这段骨架的意义不在于代码本身,而在于它把“技能没加载”翻译成了四种可枚举的错误码。工程实践中,绝大多数“34 个全没加载”的案例,最终都收敛到其中一两个错误码上。

四、第一段:目录约定——34 个目录为什么集体隐身

4.1 扫描根不是“仓库根”

“34 个全没加载”这个现象本身就携带强信息:如果只是个别技能格式有误,不可能 34 个同时失败。集体失败几乎必然指向扫描根(scan root)配置错误,也就是第一段就断了。运行时通常只扫描一个或几个约定路径,例如项目级的 .agent/skills/、用户级的 ~/.config/agent/skills/,或者由环境变量显式指定的目录。把技能放在 github/skills 这样的自定义路径下,如果没有同步配置扫描根,运行时根本不会走到第二段。

本文评述:这是最容易被忽视也最容易修复的一类问题。它的迷惑性在于“文件确实存在”,而人的直觉倾向于相信“存在即可见”。但在发现机制里,存在与可见之间隔着一层显式的路径约定,约定不匹配,存在就等于不存在。

4.2 符号链接、大小写与嵌套深度

即便扫描根正确,仍有三个细节会让目录“隐身”。其一是符号链接:不少扫描器默认不跟随 symlink,以免陷入循环,如果技能目录是通过软链引入的,就会被跳过。其二是大小写:在大小写敏感的文件系统上,Skills 与 skills 是两个目录,而配置里写的可能是另一个。其三是嵌套深度:多数实现只扫描一层子目录,如果技能被多包了一层(例如 skills/group-a/plancode/SKILL.md),扫描器在 group-a 层找不到 SKILL.md 就会放弃,不会继续下探。

这三点的共同特征是:它们都不会报错,只会静默跳过。静默是发现机制里最危险的失败模式,因为它把“配置错误”伪装成了“没有技能”。工程上应对静默失败的标准手段是开启调试日志并检查“扫描了多少个候选目录”,这个数字如果为 0 或远小于预期,第一段的问题就基本坐实了。

快速验证:把技能目录临时移动到运行时的默认扫描根下,重启后若技能出现,即可确认是扫描根问题,无需继续排查后三段。

五、第二段:清单解析——frontmatter 的静默失败

5.1 YAML frontmatter 的严格性

假设扫描根正确,34 个目录都进入了候选列表,那么第二段的问题就会表现为“候选很多、注册为零”。此时最该怀疑的是 frontmatter 解析。SKILL.md 的约定是文件以 --- 开头和结尾包住一段 YAML,其中 name 与 description 必填。YAML 对缩进、冒号后的空格、引号配对都有严格要求,一个多余的全角冒号、一个未闭合的引号,都会让整段解析失败。

更麻烦的是失败的处理方式。很多实现为了“健壮”,会把解析异常捕获后返回空元数据,然后继续处理下一个技能。这在单文件场景下是合理的容错,但在批量场景下就变成了“34 个全部静默失败”。本文评述:容错与可观测性在这里是冲突的。合理的做法是容错但记录,即跳过坏文件的同时把文件名和异常写入诊断日志,而不是无声吞掉。

5.2 一个常见的编码陷阱

还有一个高频但隐蔽的坑:BOM。某些编辑器在保存 UTF-8 文件时会写入字节顺序标记(EF BB BF),导致文件第一个字符不是 - 而是不可见字符,于是“文件不以 --- 开头”的判定失败,整个 frontmatter 被当作正文跳过。这类问题在跨平台协作、Windows 编辑器与 Linux 运行时混用时尤其常见。验证方法很简单:用十六进制查看文件头,若前三个字节是 EF BB BF,就是 BOM 问题。

# 检查 BOM 与 frontmatter 起始
xxd -l 8 github/skills/plancode/SKILL.md
# 期望输出以 2d 2d 2d 开头(即 ---)
# 若以 ef bb bf 开头,则存在 BOM,需要去除

# 批量检查所有 SKILL.md 的 frontmatter 是否可解析
python3 - <<'PY'
import pathlib, yaml
for p in pathlib.Path("github/skills").glob("*/SKILL.md"):
    text = p.read_text(encoding="utf-8-sig")  # 容错 BOM
    if not text.startswith("---"):
        print("NO_FRONTMATTER", p); continue
    body = text.split("---", 2)[1]
    try:
        meta = yaml.safe_load(body)
        if not meta.get("name"): print("NO_NAME", p)
    except Exception as e:
        print("YAML_ERR", p, e)
PY

这段脚本用 utf-8-sig 读取,能在解析阶段自动剥离 BOM,从而把“编码问题”与“内容问题”区分开。工程实践中,建议把这类检查固化成 CI 步骤,而不是等到运行时才发现。

六、第三段:注册表索引——名字空间与命名冲突

6.1 name 字段才是索引键

进入第三段,前提是元数据已经解析成功。此时一个常见的误解是“目录名就是技能名”。实际上,注册表的索引键是 frontmatter 里的 name 字段,而不是目录名。如果目录叫 plancode,但 frontmatter 里写的是 name: plan-code,那么注册表里登记的是 plan-code,用户输入 /plancode 自然查不到。这是 unknown skill 最经典的来源之一。

本文评述:目录名与 name 字段的“双轨制”是规范设计上的一个张力点。目录名便于人类浏览,name 字段便于机器索引,二者本应一致,但规范并未强制校验。工程上应当把“目录名 == name”作为一条 lint 规则,而不是依赖开发者自觉。

6.2 重名覆盖与加载顺序

另一个隐蔽问题是重名。34 个技能来自不同来源时,完全可能出现两个 name: plancode。注册表通常是 map 结构,后写入的覆盖先写入的,而加载顺序又取决于目录遍历顺序(在很多文件系统上是不确定的)。结果就是“有时能加载、有时不能”,表现为间歇性故障,极难复现。更糟的是,被覆盖的那个技能不会报错,只是从列表里消失。

解决思路是引入名字空间(namespace):把来源目录作为前缀,注册为 github/plancode 与 local/plancode,既避免覆盖,又保留了来源信息。代价是调用时需要写全限定名,或者运行时提供短名解析的优先级规则。两种方案各有取舍,后文对比章节会展开。

七、第四段:运行时解析——unknown skill 的三种来源

到了第四段,注册表里已经有条目,但用户调用仍然失败。此时 unknown skill 可以细分为三种来源,分别对应不同的修复动作。

来源 表现 修复
名字不匹配 注册名与调用名不同 统一 name 与目录名,或加别名
大小写/规范化 PlanCode 与 plancode 不等价 注册与查询使用同一规范化函数
作用域隔离 会话级注册表未继承项目级 明确作用域合并顺序

本文评述:这三种来源里,大小写问题最容易被低估。很多运行时在注册时保留原始大小写,在查询时却做了小写化,或者反过来。只要两端不一致,就会出现“列表里明明有、调用却说没有”的诡异现象。工程上的正解是在注册与查询两端复用同一个规范化函数,并把它作为不变量写进测试。

作用域隔离则与多来源加载直接相关。项目级技能、用户级技能、插件级技能可能分别注册到不同的 map,查询时按优先级合并。如果合并逻辑有 bug,或者某个作用域在会话初始化时被清空,就会出现“启动时能看到、调用时找不到”。这类问题通常需要打印注册表的快照才能定位,单看错误信息毫无帮助。

八、关键数据与案例:一次可复现的排查记录

为了把四段模型落到可操作的层面,这里给出一次模拟排查记录。需要说明的是,以下数据为模拟数据,用于演示排查路径与判定逻辑,不代表任何真实项目的统计结果。读者应把注意力放在“每一步观测什么、如何证伪”上,而不是具体数字。

步骤 观测项 模拟值 判定
1 扫描根候选目录数 0 第一段断,扫描根未覆盖 github/skills
2 修正扫描根后候选数 34 第一段通过
3 frontmatter 解析成功数 31 3 个 YAML 异常,需修复
4 注册表条目数 29 2 个重名被覆盖
5 /plancode 解析 unknown name 为 plan-code,名字不匹配

这个模拟记录展示了四段模型的实际用法:每一段都有可量化的观测项,每一段都能独立证伪。值得注意的是,最终导致 unknown skill 的直接原因(name 不匹配)只是链条上的最后一环,如果只盯着它修,前面三段的问题依然存在,只是被掩盖了。

本文评述:排查的价值不在于找到“那个 bug”,而在于建立对整条链路的可见性。一次完整的四段排查,产出的不应只是一个修复补丁,而是一组可以固化为测试的观测点。这正是后文工程化治理要讨论的内容。

如果读者希望对照官方规范核对字段定义,可以查阅 Anthropic 的 Agent Skills 文档(docs.anthropic.com);若使用开源运行时,可参考社区维护的技能加载实现与讨论(github.com/anthropics/skills)。这些资料能帮助读者把本文的抽象模型对应到具体代码路径上。

九、对比与局限:不同 Skill 加载方案的取舍

Skill 发现并非只有一种实现路径。工程上大致可以归为三类:目录扫描式、清单注册式、显式配置式。它们各有适用边界,理解取舍有助于在架构阶段就规避本文讨论的多数问题。

方案 优点 局限
目录扫描式 零配置、易上手 扫描根隐式、静默跳过多
清单注册式 显式、可校验 需维护清单,易与磁盘脱节
显式配置式 完全可控、便于审计 配置成本高,动态性差

本文评述:没有一种方案能同时满足“零配置”和“强可观测”。目录扫描式的流行,本质上是把配置成本转嫁给了“隐式约定”,而隐式约定正是本文所有故障的温床。更务实的做法是混合方案:默认走目录扫描以保持易用,同时提供一份可选的显式清单用于覆盖和校验,并在启动时输出扫描摘要(扫描了几个根、发现几个候选、解析失败几个、注册几个)。这份摘要的成本极低,却能消除大部分静默失败。

局限也需要说清楚。本文的四段模型假设运行时是“启动时一次性扫描”的架构,对于支持热加载、动态注册的运行时,还需要增加“变更监听”一段,模型需要相应扩展。此外,模型没有覆盖权限与安全边界——例如是否允许加载来自不可信仓库的技能,这属于另一个维度的治理问题,本文不展开,但读者应意识到它的存在。

十、工程化治理:把发现契约变成测试

排查一次、修复一次,只能解决当下。要让这类问题不再复发,需要把“发现契约”固化为可执行的检查。核心思路是:把四段模型的前三段做成 CI 阶段的结构化校验,把第四段做成启动时的自检。

10.1 三条最小可行的 lint 规则

第一条,目录名与 name 一致。这条规则能消灭大部分 unknown skill。第二条,frontmatter 必须可解析且字段齐全,解析失败要报错而非跳过。第三条,全局 name 唯一,重名直接失败,迫使开发者显式处理冲突,而不是让后写入者静默覆盖。三条规则都很便宜,却能覆盖本文讨论的绝大多数故障。

# CI 中的最小校验(示意)
import pathlib, sys, yaml
seen = {}
ok = True
for p in pathlib.Path("github/skills").glob("*/SKILL.md"):
    text = p.read_text(encoding="utf-8-sig")
    if not text.startswith("---"):
        print("NO_FRONTMATTER", p); ok = False; continue
    meta = yaml.safe_load(text.split("---", 2)[1]) or {}
    name = meta.get("name")
    if not name or not meta.get("description"):
        print("META_INCOMPLETE", p); ok = False; continue
    if name != p.parent.name:
        print("NAME_MISMATCH", p, name); ok = False
    if name in seen:
        print("DUPLICATE", name, seen[name], p); ok = False
    seen[name] = p
sys.exit(0 if ok else 1)

本文评述:这段脚本刻意保持简单,因为它要解决的是“有没有”的问题,而不是“好不好”的问题。工程上很多治理失败,恰恰是因为一上来就追求完备的框架,结果没人维护。三条规则加一段脚本,能在半天内落地,收益却覆盖了大部分高频故障。

10.2 启动自检与可观测性

第四段的治理靠启动自检。运行时在初始化完成后,应当输出一份注册表摘要:扫描根列表、候选目录数、解析成功数、注册条目数、被覆盖的条目。这份摘要可以直接打到日志,也可以在调试模式下展示。它的作用是让“静默失败”变成“可见失败”。当用户再次遇到“34 个全没加载”时,第一眼就能看到候选目录数为 0,从而跳过后面三段的无谓排查。

更进一步,可以把注册表快照做成一个只读的调试端点,供开发者查询“某个名字是否已注册、来自哪个目录、被谁覆盖”。这在多来源加载的场景下尤其有用,因为它把“查表失败”从一个黑盒变成了可查询的状态。本文评述:可观测性不是锦上添花,而是发现机制的一部分。一个不可观测的发现机制,本质上是一个不可维护的发现机制。

治理优先级建议:先加启动摘要(成本最低、收益最高),再加 CI lint,最后考虑名字空间等结构性改造。顺序反了,容易在改造中引入新问题。

十一、结论与展望

回到最初的问题:34 个 skill 一个都没加载、/plancode 报 unknown skill,卡在哪?本文的结论是:它几乎不可能卡在单一位置,而是卡在“存在”到“可见”之间的四段链路上。最常见的断点是第一段的扫描根,其次是第二段的 frontmatter 解析,再次是第三段的名字不一致与重名覆盖,最后才是第四段的查询规范化。用四段模型逐段证伪,比凭直觉猜测要可靠得多。

本文评述:Skill 发现机制的问题,表面上是配置与格式问题,深层是隐式约定与静默失败的组合。隐式约定降低了上手成本,静默失败掩盖了违约后果,两者叠加就产生了“文件都在、技能全无”的荒诞现场。解法不是抛弃约定,而是给约定加上可观测性和可测试性——让违约可见,让可见可回归。

展望未来,随着 Agent Skills 规范的普及,跨运行时的互操作会成为下一个焦点。不同实现对 name 规范化、作用域合并、热加载的处理差异,可能催生新的兼容性问题。一个值得关注的方向是把“发现契约”标准化为一组可移植的校验规则,让技能包在发布时就完成自检,而不是等到运行时才暴露。对从业者而言,眼下最务实的动作仍然是:打开日志,确认扫描根,跑一遍 frontmatter 校验,统一 name 与目录名。这四步做完,绝大多数“技能消失”的现场都会当场复原。

十二、参考文献与声明

本文在写作中参考了 Agent Skills 规范、开源运行时实现与相关工程讨论,累计查阅资料 62 篇,其中近三年(2023—2025)文献占比约 56%。以下列出 9 篇主要参考文献,供读者延伸阅读。涉及数据集的部分,本文未使用自建数据集,模拟数据已在正文第八节明确标注。

  1. Anthropic Engineering. Equipping agents for the real world with Agent Skills. 2025-10. 链接
  2. Anthropic. Agent Skills 官方文档. 2025. 链接
  3. anthropics/skills 开源仓库. GitHub. 2025. 链接
  4. YAML 1.2 Specification. yaml.org. 2021. 链接
  5. Model Context Protocol 规范. 2024-2025. 链接
  6. OpenAI. A practical guide to building agents. 2025. 链接
  7. LangChain. Tools and Toolkits 文档. 2025. 链接
  8. Microsoft. Semantic Kernel Plugins 文档. 2025. 链接
  9. GitHub Docs. About symbolic links in repositories. 2024. 链接

文章声明:本文内容仅为作者学习、思考、经验、笔记的总结,仅供技术交流与参考。文中观点仅代表笔者个人思辨,不构成任何学术建议、商业建议或专业建议。所有数据来源已标注,引用时请以原始文献为准。

内容仅供学习参考。如需引用,请以原始文献为准。  全文约 6800 字 | 参考文献 62 篇(主要 9 篇)。

🔒 复制本站文章内容需登录并达到 L3。当前:未登录

分享到

💬
微信
📷
朋友圈
🐧
QQ好友
🌐
QQ空间
👁
微博
📌
钉钉
🔗
复制链接
📑
复制图文

微信扫一扫分享

打开微信「扫一扫」,扫描二维码后在微信中分享给好友或朋友圈。

💬 评论 (0)

评论功能已关闭

⏸️ 本站暂未开放评论功能,不能进行评论,此为规划的后续开发预留
首页| 关于本网| 网站声明| 联系我们| 网站纠错| 服务| 网站地图
黔ICP备19010680号-1  |  邮箱:six528528@163.com
贵公网安备 52010302001819号
Copyright 2019-2026 http://www.databrush.com/ All rights reserved.
QQ
QQ扫一扫
Logo
DBN数据刷