上一章创建了:
examples/skills/error-log-analysis/SKILL.md
但仅仅把文件放在磁盘上,并不代表桌面 Agent 一定能看到它。本章沿着真实源码回答:
Pi 从哪些位置找 Skill,怎样解析,重名时谁赢,失败后又怎样报告?
1. 加载链路总览
flowchart LR
SRC["Settings / Auto / 配置 Package"] --> PM["PackageManager.resolve"]
EP["additionalExtensionPaths"] --> EPS["resolveExtensionSources"]
ADD["--skill / SDK Additional"] --> PATHS["Resource Loader 有序路径"]
DISC["resources_discover"] --> EXT["extendResources"]
PM --> PATHS
EPS --> PATHS
PATHS --> RL["ResourceLoader.updateSkillsFromPaths"]
EXT --> RL
RL --> LS["loadSkills"]
LS --> FILE["loadSkillFromFile"]
FILE --> META["Skill Metadata + Diagnostics"]
META --> PROMPT["System Prompt 的可见目录"]
这条链可分为两类职责:
- Package Manager:决定候选路径、启用状态、来源和优先级;
- Skill Loader:扫描文件、解析 Frontmatter、去重名称并生成诊断。
把它们混成一个“扫描目录”会看不懂 Settings、Package 和 CLI 为什么能参与。
2. 默认 Skill 来源
Pi 文档列出:
用户级
~/.pi/agent/skills/
~/.agents/skills/
项目级
<cwd>/.pi/skills/
<cwd或祖先>/.agents/skills/
其他来源
- Package 的
skills/或package.json中的pi.skills; - 用户/项目 Settings 的
skills数组; - 重复使用的
--skill <path>; - SDK 的
additionalSkillPaths; - Extension 的
resources_discover。
这些来源最终都被规范化成文件或目录路径,不是分别实现六套加载器。
3. 项目 Trust 的作用
项目自动发现只有在项目受信任时加入:
<cwd>/.pi/skills;cwd和祖先的.agents/skills;- 项目级 Settings / Package 来源。
项目 .agents/skills 的祖先搜索:
- 从
cwd开始; - 每层加入
.agents/skills; - 如果发现 Git Repo Root,到 Repo Root 为止;
- 如果不在 Git Repo 中,一直走到文件系统根。
flowchart TD
C["cwd/.agents/skills"] --> P["parent/.agents/skills"]
P --> G{"到 Git Root?"}
G -->|否| P2["继续向上"]
G -->|是| STOP["停止"]
注意这个顺序是从当前目录向祖先排列,较具体的路径先进入候选。
4. .pi/skills 与 .agents/skills 的扫描差异
源码有两种 Discovery Mode:
type SkillDiscoveryMode =
| "pi"
| "agents";
共同规则:
- 递归查找目录中的
SKILL.md; - 跳过点开头目录;
- 跳过
node_modules; - 跟随可用的文件/目录 Symbolic Link;
- 遵守
.gitignore、.ignore、.fdignore。
自动发现时的差异在 Root Markdown:
| 目录模式 | 根目录直接 .md 文件 |
|---|---|
~/.pi/agent/skills、项目 .pi/skills |
作为独立 Skill 候选 |
~/.agents/skills、项目 .agents/skills |
忽略,要求目录中的 SKILL.md |
这对应文档对跨 Harness 共享目录的兼容约定。
这个规则属于 Package Manager 的自动发现 Mode,不是物理目录的永久属性。若 Settings、
--skill 或 SDK Additional 显式传入同一个 .agents/skills 目录,路径可能走 Pi Mode 或
loadSkillsFromDirInternal(..., true),此时根 .md 可以被接受。
5. 遇到 SKILL.md 后为什么不再向下
collectSkillEntries() 会先检查当前目录是否存在 SKILL.md。一旦找到,就把它加入并立即
返回,不再递归扫描这个目录的子目录。
parent/
├── SKILL.md ← parent 自己是一个 Skill
└── nested/
└── SKILL.md ← 这次不会作为独立 Skill 发现
原因是带入口文件的目录已经被视为一个自包含能力包;其中的子目录应是它自己的资源。
如果要发布两个 Skill,应使用同级的两个 Skill Root。
6. Ignore 规则怎样传播
扫描从 Root 建立 Ignore Matcher。进入子目录时,会读取该层:
.gitignore
.ignore
.fdignore
规则被加上相对 Root 的 Prefix,再继续向下传递。空行和普通注释被忽略,否定规则保留。
这意味着可以让 Skill 仓库排除:
- 生成目录;
- 大型 Fixture;
- 私密本地文件;
- 不想暴露给 Agent 的实验 Skill。
Ignore 只是发现过滤,不是访问控制。若模型通过其他路径知道文件位置,Tool Policy 仍决定 能否读取。
7. Package Manager 先处理来源
DefaultPackageManager.resolve() 的大致顺序:
- 收集项目和用户 Packages;
- 解析 Package Resources;
- 处理项目 Settings 中的本地资源;
- 处理用户 Settings 中的本地资源;
- 加入自动发现资源;
- 按 Precedence Rank 排序;
- 对规范化后的相同物理路径去重。
结果不是简单 string[],每项还有:
type ResolvedResource = {
path: string;
enabled: boolean;
metadata: PathMetadata;
};
Metadata 记录 Scope、Source、Origin 和 Base Directory,后续用于冲突和来源展示。
8. 正常资源优先级
源码的 Rank 从高到低:
| Rank | 来源 |
|---|---|
| 0 | 项目 Settings 本地条目 |
| 1 | 项目自动发现 |
| 2 | 用户 Settings 本地条目 |
| 3 | 用户自动发现 |
| 4 | Package Resource |
排序的目的不是直接覆盖文件内容,而是配合后面的“同名 Skill 第一个获胜”。
Resource Loader 中名为 cliEnabledSkills 的变量容易误导:它来自
additionalExtensionPaths 所解析的临时 Extension/Package,其中可以携带 Skill;它不是
命令行 --skill。
真正的 --skill 在 main.ts 中被解析成 additionalSkillPaths,与 SDK
additionalSkillPaths 走同一条尾部列表。正常模式的组合顺序是:
临时 Extension/Package 携带的 Skill
→ Normal Skill
→ --skill / SDK additionalSkillPaths
由于同名采用 first-wins,显式 --skill 并不天然覆盖普通资源。
9. 路径去重与名称去重不是一回事
路径去重
Package Manager 和 Resource Loader 会规范化、Canonicalize 路径。同一个文件通过 Symbolic Link 或不同文字路径重复出现时,只保留一份。
名称去重
两个不同文件都可以声明:
name: error-log-analysis
loadSkills() 用 Map<name, Skill>,第一个成功加载的 Skill 获胜;后一个产生 Collision
Diagnostic。
flowchart LR
A["project/error/SKILL.md\nname=error-log-analysis"] --> M{"Skill Map"}
B["package/error/SKILL.md\nname=error-log-analysis"] --> M
M --> W["保留排在前面的 Skill"]
M --> D["为后者记录 collision"]
10. Resource Loader 怎样组合路径
DefaultResourceLoader.reload():
- 调用 Package Manager 解析正常资源;
- 单独解析
additionalExtensionPaths的临时 Extension/Package Sources; - 只取 Enabled Resources;
- 如果未禁用 Skills,组合
Extension/Package Skills + Normal + additionalSkillPaths; - 交给
updateSkillsFromPaths(); - 保存 Skill 与 Diagnostics。
合并过程按 Canonical Path 去重,保留第一次出现的位置。
Extension 后续通过 extendResources() 添加 Skill Path 时,会把新路径合并到已加载路径尾部,
再重新执行 Skill Loading。若与已有 Skill 同名,仍是前者获胜。
11. --no-skills 到底禁用什么
设置 noSkills 后,正常自动/配置资源不会进入组合列表。
但仍有例外:
additionalExtensionPaths显式加载的临时 Extension/Package 所携带的 Skill;- CLI
--skill和 SDKadditionalSkillPaths; - 后续 Extension
resources_discover返回的 Skill Paths。
源码在 noSkills 分支仍组合 Additional Paths;extendResources() 也不因 noSkills
拒绝 Extension Skill,只要最终路径数组非空,updateSkillsFromPaths() 仍会加载。因此它更接近:
禁用普通 Skill Discovery,但保留显式 Additional 和 Extension 运行期扩展。
这与文档中“--skill 即使配合 --no-skills 仍是 Additive”的说明一致。
12. loadSkills 接收什么
Resource Loader 调用:
loadSkills({
cwd,
agentDir,
skillPaths,
includeDefaults: false,
});
这里 includeDefaults: false 很关键:默认目录已经由 Package Manager 解析过,不应在
loadSkills() 中再次无条件扫描。
loadSkills() 对每个 Path:
- 不存在:产生 Warning;
- 是目录:递归扫描;
- 是
.md文件:直接尝试解析; - 是其他文件:产生 Warning;
- 读取/Stat 失败:产生 Warning。
13. loadSkillFromFile 怎样生成 Metadata
读取完整文件后:
flowchart TD
F["Markdown"] --> FM["parseFrontmatter"]
FM --> D{"description 有效?"}
D -->|否| SKIP["Diagnostic + Skip"]
D -->|是| N["name 或父目录回退"]
N --> V["名称/长度校验"]
V --> S["Skill Metadata"]
生成的核心对象包含:
interface Skill {
name: string;
description: string;
filePath: string;
baseDir: string;
sourceInfo: SourceInfo;
disableModelInvocation: boolean;
}
正文没有保存在这个 Metadata 对象中。文件虽为了解析 Frontmatter 被读取过,正文要进入
模型 Context 仍需后续 read 或显式 Command 展开。
14. 诊断分为哪些类型
课程可以按产品展示分成:
| 类型 | 示例 | 是否一定阻止加载 |
|---|---|---|
| Warning | 名称非法、描述过长、文件读取失败 | 否;取决于问题 |
| Error | 某些 Resource Loader/Package 级硬错误 | 对应资源不可用 |
| Collision | 同名 Skill 后者失败 | Winner 仍可用 |
普通本地 additionalSkillPaths 不存在时,loadSkills() 已先生成
warning: skill path does not exist;Resource Loader 看到同路径已有诊断后不会再追加
Error。因此当前这类缺失路径的实际诊断是 Warning。
源码的 ResourceDiagnostic 还携带 Path;Collision 包含 Winner 和 Loser Path。桌面 Skill
管理页应把来源和冲突双方都展示出来。
只显示“加载了 12 个 Skill”会隐藏第 13 个为什么消失。
15. 多数校验为何宽容
Pi 对 Agent Skills Standard 的多数违反只警告,仍然加载:
- 名称格式;
- 名称长度;
- Description 过长。
缺失 Description 则跳过,因为模型可见目录失去了路由信息。无效 YAML 也无法安全得到 Frontmatter,因此跳过。
宽容加载有利于兼容旧 Skill,但宿主可以在团队策略中把 Warning 升级为发布阻断。
16. SourceInfo 有什么用
每个成功 Skill 会带来源信息,例如:
- local / project;
- local / user;
- Package Source;
- CLI 或 Extension 临时来源;
- Base Directory。
Resource Loader 优先使用路径解析阶段保存的 Metadata,找不到时才根据路径推断默认来源。
但当前 v0.82.0 有一个动态扩展边界:metadataByPath 是 reload() 的局部 Map。
extendResources() 加入新路径后会重新加载全部 Skill,却没有把旧 Map 再传给
updateSkillsFromPaths()。结果是:
- 新增 Extension Path 可从
extensionSkillSourceInfos恢复正确来源; - 先前 Package、
.agents或临时 Extension/Package Skill 可能退化成source: "path"、Temporary Scope。
所以 SourceInfo 是重要诊断信息,但桌面 UI 不能假定运行期扩展后所有旧来源都保持稳定。
SourceInfo 不直接改变模型执行逻辑,但对桌面产品很重要:
- 告诉用户能力从哪里来;
- 判断是否属于项目;
- 显示 Package 或 Extension 名称;
- 为禁用、冲突和安全审查提供证据。
17. 渐进式加载的准确含义
磁盘扫描阶段:读取文件以解析 Frontmatter
运行时 Metadata:保留名称、描述、路径等
System Prompt:仅放模型可见 Skill 的轻量目录
任务匹配:模型 read 正文,或 /skill:name 显式展开
引用资源:再按需读取
“按需加载正文”不是说启动时从未读取文件字节,而是说正文不会在启动时常驻模型 Context。
同时还必须满足:
read是 Active Tool;- Skill 没有
disable-model-invocation。
否则 Metadata 可以在宿主中已加载,但不会进入模型的自动发现目录。
18. Reload 与 Extension 资源
DefaultResourceLoader.reload() 会重新:
- 解析 Settings;
- 解析 Package 和本地资源;
- 扫描 Skill;
- 重建 Diagnostics;
- 重建 Skill/Diagnostics;Session 随后的装配才负责 Prompt。
同时它会清空 Extension SourceInfo Map,并从普通来源重新建立路径;上一次
resources_discover 加入的路径不会仅靠 Loader Reload 自动恢复。
启动时,宿主调用 session.bindExtensions(...) 会触发 session_start 和
resources_discover。Extension 返回的 Skill Paths 由 extendResources() 加入并立即
重新加载,随后 Session 重建 Base Prompt。
AgentSession.reload() 只有在 UI Context、Command Context Actions、Shutdown Handler 或
Error Listener 至少一种 Binding 被保留时,才再次触发 session_start(reason="reload") 和
资源发现。只调用 bindExtensions({}) 虽能完成启动发现,但 Reload 时 hasBindings 为假,
Extension Skill 不会自动恢复。
因此桌面 UI 的 Skill 列表应订阅资源更新,而不是假设启动时永远固定。
19. 用上一章示例走一遍
假设宿主把:
examples/skills/error-log-analysis
作为 additionalSkillPaths:
- Resource Loader 解析成绝对路径;
- Path 存在且是目录;
- 扫描器在根找到
SKILL.md后停止向下递归; - Frontmatter Parser 得到 Name 和 Description;
- 名称与 Description 校验通过;
- 生成
Skill,Base Directory 是示例目录; - 没有同名 Winner,所以加入结果;
readActive 时进入<available_skills>;- 模型匹配任务后读取正文;
- 正文再引导读取 Reference 和运行 Script。
sequenceDiagram
participant H as Desktop Host
participant R as ResourceLoader
participant L as Skill Loader
participant P as System Prompt
participant M as Model
H->>R: additionalSkillPaths
R->>L: normalized path
L-->>R: Skill + diagnostics
R-->>P: visible metadata
P-->>M: name / description / location
M->>L: read SKILL.md when matched
20. 桌面 Skill 管理页
建议至少显示:
type SkillView = {
name: string;
description: string;
filePath: string;
source: string;
scope: string;
modelVisible: boolean;
diagnostics: string[];
};
这是产品侧视图,不是 Pi 原样类型。
modelVisible 不能只看 Skill 本身,还要结合:
- Active Tools 中有没有
read; disableModelInvocation;- 当前 Base Prompt 是否已重建。
21. 常见误区
“Pi 只扫描一个固定 Skills 目录”
错误。来源包括用户、项目、Settings、Package、CLI、SDK 与 Extension。
“越靠近 cwd 的 .agents Skill 一定覆盖”
只有在同名且路径顺序确实排在前面时才由 first-wins 生效;不要脱离完整来源优先级推断。
“同一路径去重等于同名去重”
错误。它们发生在不同阶段。
“有 Warning 就一定没加载”
错误。多数规范 Warning 仍加载,缺失 Description 等情况才跳过。
“noSkills 会屏蔽所有 Skill”
错误。Additional Extension/Package 携带的 Skill、--skill / SDK Additional,以及后续
Extension resources_discover 都仍可加载。
“渐进式加载表示启动时完全不读 SKILL.md”
错误。启动时读取 Frontmatter;渐进的是正文进入模型 Context。
22. 本章小结
- Package Manager 先解析来源、启用状态、优先级与路径,Skill Loader 再解析文件;
- 默认来源包括用户、受信任项目、Package、Settings、CLI、SDK 和 Extension;
- 自动发现的
.piMode 接受根.md,.agentsMode 不接受;显式目录可能走不同模式; - 当前目录有
SKILL.md时,该目录成为 Skill Root,不继续发现嵌套 Skill; - 扫描遵守三类 Ignore File,但 Ignore 不是权限控制;
- 路径去重使用 Canonical Path,名称冲突使用 first-wins 并产生 Collision;
- 正常优先级是项目 Settings、项目 Auto、用户 Settings、用户 Auto、Package;
--skill与 SDK Additional 位于普通路径之后,不天然覆盖同名 Skill;noSkills不屏蔽 Additional Extension/Package、显式 Skill Additional 或后续 Extension Discovery;- 缺失 Description 或解析失败会跳过,多数格式问题只 Warning;
- 运行时 Metadata 不保留正文,正文按需进入模型 Context;
- Loader Reload 会丢弃旧 Extension Discovery Path;Session Reload 是否重新发现取决于 四类保留 Binding。
- extendResources 重载全部 Skill 时,旧 metadataByPath 不会重传,部分既有 SourceInfo 可能退化。
23. 自测
- Package Manager 与 Skill Loader 分别负责什么?
.pi/skills与.agents/skills对根.md的规则有何不同?- 目录自身含
SKILL.md时,为什么不继续发现子目录 Skill? - 路径重复与名称冲突分别怎样处理?
- 正常资源的五级 Precedence 是什么?
noSkills后哪些 Additional/Extension 来源仍能加载?- 为什么“启动时读取文件”不违背渐进式披露?
- 桌面管理页怎样判断一个 Skill 是否对模型可见?