上一章已经让 error-log-analysis 成功出现在 Resource Loader 中。现在用户只输入:
请分析这份服务日志为什么反复超时。
桌面 Agent 没有收到 Skill 名称。它能否自己选中正确能力?选中后又是谁“执行” Skill?
答案先分成两类语义:
- 自动匹配:模型看目录、决定读取;
- 显式调用:应用明确指定 Skill。
显式调用又有两套公开接法:通用 AgentHarness.skill() 与 coding-agent
AgentSession 的 /skill:name 展开。它们的加载时机和失败语义不同。
1. Skill 没有独立执行器
先建立最重要的认识:
Skill 通常不是一个 execute() 函数
两层 Skill 都没有 execute 字段,但字段并不完全相同:
- coding-agent 的
Skill保存name、description、filePath、baseDir、sourceInfo和可见性标记,正文仍在文件中; - 通用 Harness 的
Skill保存name、description、content、filePath和可见性 标记,没有内置的baseDir、sourceInfo字段。
flowchart LR
S["Skill 指令"] --> A["Agent Loop"]
A --> R["read 正文/参考资料"]
A --> T["调用已有 Tool"]
T --> O["Tool Result"]
O --> A
A --> U["最终回答"]
所谓“执行 Skill”,实际是模型读懂工作流后,在普通 Agent Loop 中选择 Tool 并继续多轮推理。
2. coding-agent 自动匹配的前提
自动路径要求:
- Resource Loader 已加载 Skill;
- Skill 没有
disable-model-invocation: true; read是 Active Tool;- Base System Prompt 已在资源变化后重建;
before_agent_start后的最终有效 System Prompt 仍保留这份目录。
满足时,System Prompt 才包含:
<skill>
<name>error-log-analysis</name>
<description>...</description>
<location>/absolute/path/SKILL.md</location>
</skill>
Extension 可以在 Run 前替换 Base System Prompt,并移除 Skills Section。缺少任一条件, 模型就不能通过这个目录自动发现 Skill。
3. 通用 AgentHarness 怎样提供自动目录
通用 AgentHarness 的默认 System Prompt 只是:
You are a helpful assistant.
resources.skills 会提供给宿主的 systemPrompt 回调,但 Harness 不会自动把它们写入
Prompt。宿主要明确组合:
systemPrompt: ({ resources }) =>
[
"You are a desktop agent.",
formatSkillsForSystemPrompt(resources.skills ?? []),
]
.filter(Boolean)
.join("\n\n")
宿主还要提供一个模型可调用、并且能读取 skill.filePath 的文件读取 Tool。
formatSkillsForSystemPrompt() 会过滤 disableModelInvocation,但它本身既不检查 Active
Tool,也不要求 Tool 必须叫 read。这些是通用 Harness 交给宿主负责的能力边界。
目录进入 Prompt 以后,“是否匹配”仍然是模型行为,不是 Harness 内置 Router。
4. 模型怎样决定匹配
System Prompt 给模型的规则是:
Use the read tool to load a skill's file
when the task matches its description.
模型比较当前任务语义与 Description:
flowchart TD
U["用户任务"] --> M["模型读取 available_skills"]
M --> Q{"Description 匹配?"}
Q -->|是| R["read location"]
Q -->|否| N["不用该 Skill"]
Pi Core 没有额外的关键词打分器、Embedding Retriever 或确定性 Skill Router。决定来自模型 本身。
5. 为什么 Description 像路由表
模型在第一次选择时只看到 Name、Description 和 Location,还没看到正文。
# 路由信息不足
description: Helps with logs.
# 同时说明 What 与 When
description: Analyzes application error logs, groups repeated
signatures, reconstructs timelines, and proposes evidence-backed
next checks. Use when the user provides a log file or asks to
diagnose runtime errors from logs.
任务对象、主要产出和触发语境越清楚,模型越容易正确选择。
6. 自动匹配不是保证
即使 Description 很好,模型也可能:
- 没有读取 Skill;
- 选错相似 Skill;
- 同时读取多个 Skill;
- 直接依据已有知识回答。
Pi 文档明确说明模型并不总会自动读取匹配 Skill。
所以产品不能把“目录已加载”展示成“工作流一定执行”。更准确的状态是:
Skill available
→ Skill selected
→ SKILL.md loaded
→ workflow followed
每一步都需要单独观察。
7. 自动路径的正文何时进入 Context
模型输出普通 Tool Call:
read({
path: "/.../error-log-analysis/SKILL.md"
})
Read Tool 返回本次读取内容,形成 Tool Result。之后:
- Tool Call 与 Result 进入 Transcript;
- 下一次模型调用能看到这次 Tool Result;
- Session 正常持久化这些消息;
- Compaction 时它们可能被摘要或保留。
Pi 没有单独的“Active Skill Context”存储区。正文通过普通消息进入 Context。
coding-agent 内置 Read Tool 默认最多返回 2000 行或 50KB,以先触发者为准。小 Skill 通常
一次得到完整正文;更大的文件会附带下一次 offset,模型必须继续分页读取。不能把一次
Read Tool Call 等同于无条件获得完整 Skill。
8. 自动读取后的相对路径
System Prompt 还告诉模型:
Skill 引用的相对路径从 Skill Directory 解析,
Tool Command 使用 Absolute Path。
因此看到:
Read `references/report-format.md`.
模型应组合 dirname(SKILL.md) 与相对路径,并在 Tool Command 中使用绝对路径。
引用文件不会被 read SKILL.md 自动递归加载。模型要在工作流需要时再调用 Tool。
9. 显式调用绕过模型选择
用户输入:
/skill:error-log-analysis 分析 server.log
这条路径不要求模型先从 Description 判断。AgentSession._expandSkillCommand() 在请求模型
之前处理它。
sequenceDiagram
participant U as 用户
participant S as AgentSession
participant F as SKILL.md
participant A as Agent
U->>S: /skill:name args
S->>S: 按 name 查 Skill
S->>F: readFileSync
F-->>S: Frontmatter + Body
S->>S: stripFrontmatter + 包装
S->>A: Expanded User Message
显式调用确定的是“正文被展开”,并不保证模型随后每一步都严格遵守。
10. 通用 AgentHarness 的显式调用
构建桌面 Agent 若直接使用通用 Harness,可以调用:
await harness.skill(
"error-log-analysis",
"分析 /absolute/path/server.log",
);
通用 Harness 的 Skill Loader 在资源加载阶段已经把正文保存在 skill.content。
harness.skill() 创建当前 Turn State,在 Resources 中按 Name 查找,再用
formatSkillInvocation() 把内存中的 Content 和 Additional Instructions 组成 User
Message。它不会在每次调用时重新同步读取 SKILL.md。
磁盘文件变化后应先走宿主资源 Reload,不能假设下一次 harness.skill() 自动看到新正文。
未知名称会抛出 AgentHarnessError("invalid_argument", "Unknown skill: ...")。
11. 显式 Skill Block 的真实形状
源码构建:
<skill
name="error-log-analysis"
location="/absolute/path/SKILL.md">
References are relative to /absolute/path.
...去掉 Frontmatter 后的正文...
</skill>
分析 server.log
coding-agent 会先裁剪 /skill:name 后面的参数,再把它原样放在 Skill Block 后;通用
Harness 则把调用者传入的 additionalInstructions 原样追加,不替调用者裁剪。两者都不会
给路径参数补全,也不会自动解析成结构化 Schema。
12. 两套显式调用对照
| 行为 | 通用 AgentHarness.skill() |
coding-agent /skill:name |
|---|---|---|
| 正文来源 | 加载阶段保存在 Skill.content |
调用时 readFileSync(filePath) |
| 调用形式 | Method + Additional Instructions | User Text Command |
| 未知名称 | 抛 invalid_argument |
原文本继续 |
| 文件调用前被删除 | 仍用内存快照 | 发 skill_expansion Error,原文本继续 |
| disableModelInvocation | 仍可显式调用 | 仍可显式调用 |
| 忙碌时 | Harness 要求 idle | 普通 Prompt 可按 streamingBehavior 入队 |
两者都会构造相似的 <skill> User Message,但不能混用失败和刷新语义。
13. coding-agent 显式调用受加载结果约束
_expandSkillCommand() 只在 Resource Loader 的 Skills 数组中按 Name 查找。
未知 Skill
找不到时,原文本不变:
/skill:not-found ...
它随后会作为普通 User Text 发给模型,并不是一个 Core Error。
文件读取失败
Skill 已加载但文件后来被删除或不可读时:
- Extension Runner 收到
skill_expansionError; - 原始文本不变;
- Prompt 流程继续。
桌面宿主应显示错误事件,否则用户只会看到模型困惑地处理一条未展开命令。
14. disable-model-invocation 的真实作用
设置:
disable-model-invocation: true
只会让 formatSkillsForPrompt() 不把该 Skill 放入自动目录。
_expandSkillCommand() 查找的是全部已加载 Skills,没有再次检查这个字段。所以:
模型自动发现:不可见
用户 /skill:name:仍可展开
应用 harness.skill():仍可调用
这适合把启动权留给用户,但不是执行授权。
15. 普通 Prompt 的处理顺序
coding-agent 的普通 prompt() 大致顺序:
- 若是已注册 Extension Command,先执行并返回;
- 触发
inputExtension Event; - Handler 可以处理或转换文本;
- 展开
/skill:name; - 展开 Prompt Template;
- 若正在 Streaming,按 streamingBehavior 入 Steering/Follow-up 并立即返回;
- idle 时进行模型/API Preflight;
- idle 新 Run 才触发
before_agent_start; - 进入 Agent Loop。
所以 input Handler 看到的是 Skill 展开前文本,before_agent_start 看到的是展开后文本。
运行中排队的这条消息不会单独触发自己的 before_agent_start。
16. Steering 与 Follow-up 也能显式调用 Skill
直接调用:
await session.steer(
"/skill:error-log-analysis 改按日志工作流分析",
);
或:
await session.followUp(
"/skill:error-log-analysis 完成后再检查另一份日志",
);
两者都会先展开 Skill Command 和 Prompt Template,再把展开后的 User Message 放入基础 Agent 队列。
它们会拒绝已注册的 Extension Command,因为 Extension Command 不能作为普通排队文本执行。
直接 session.steer() / followUp() 不触发 input 或 before_agent_start;它们从
Skill/Template 展开直接进入队列。
17. 引用脚本是怎样执行的
SKILL.md 写:
Run `node scripts/summarize-log.mjs <log-path>`.
这不会自动启动进程。模型需要:
- 从 Skill Base Directory 解析绝对 Script Path;
- 决定使用
bashTool; - 形成 Tool Call;
- 宿主执行权限/审批策略;
- Tool 返回 stdout、stderr 与 Exit;
- 模型解释结果。
flowchart LR
I["Skill 中的脚本说明"] --> C["模型形成 bash Tool Call"]
C --> P{"宿主允许?"}
P -->|是| X["执行脚本"]
P -->|否| D["Tool 拒绝结果"]
X --> R["Tool Result"]
R --> M["模型继续"]
Skill 不能绕过 Tool Policy。
18. Skill 执行失败怎样处理
失败位置不同,行为也不同:
| 失败点 | 进入哪里 | 典型恢复 |
|---|---|---|
自动 read SKILL.md 失败 |
普通 Tool Result | 模型重试、换路径或询问用户 |
| 显式展开读文件失败 | skill_expansion Error,原文本继续 |
UI 提示并 Reload |
| 引用 Reference 失败 | 普通 Tool Result | 模型说明缺失或选择替代 |
| Script Exit 非零 | Bash Tool Result | 阅读 stderr、修正参数、停止 |
| Tool 被 Policy 拒绝 | Tool Result / 宿主错误契约 | 请求批准或换无副作用方案 |
| 模型没有自动选择 | 没有 Skill Tool Call | 用户显式调用或改进 Description |
没有统一的“Skill Failed Event”,因为大多数步骤就是普通 Tool/Agent 流程。
19. 多个 Skill 同时适用
例如用户说:
分析错误日志,然后把结果写成事故复盘文档。
可能同时匹配:
error-log-analysis;incident-report-writing。
自动路径中,模型可以读取两份 Skill。Pi Core 没有内置 Skill 优先级字段、互斥锁、依赖图 或自动合并器。
Skill 作者应让 Description 尽量区分场景,并在正文中说明与其他能力的组合顺序。
20. 多 Skill 的冲突怎么处理
要区分两类冲突:
名称冲突
加载阶段已经 first-wins,模型只看到 Winner。
指令冲突
两份不同名称 Skill 都被读取后,正文可能要求不同输出格式或步骤。Pi 没有确定性规则引擎 解决它们。
宿主可采用:
- 一个 Orchestrator Skill 明确组合顺序;
- 在 System Prompt 中定义不可违反的全局边界;
- 用 Extension 对高风险 Tool 做强制 Policy;
- 让用户选择目标输出。
不要把安全冲突留给自然语言优先级猜测。
21. Skill 与多轮 Agent Loop
自动路径:
Turn 1:模型决定 read SKILL.md
Tool Result:正文
Turn 2:模型按步骤采样日志
Tool Result:日志片段
Turn 3:模型运行辅助脚本
Tool Result:统计
Turn 4:模型回读证据并输出
显式路径把正文直接放在最初 User Message 中,通常少一次 read SKILL.md Tool Call,但引用
文件和业务 Tool 仍需要后续 Turn。
Skill 不改变 Agent Loop 的 Tool Call、Steering、Follow-up 或 Event 规则。
22. Skill 会一直“激活”吗
Pi 没有 Active Skill Flag。
正文进入 Transcript 后,后续 Turn 可以继续看到并遵循它。但随着 Context Transform、 Compaction、Branch/Fork、新 Prompt 和新规则,可见内容会变化。Compaction Summary 是 有损的,不保证逐字保留 Skill。
长期必须遵守的安全规则不应只存在某次 Skill Body 中,应在 System Prompt、Project Context 或 Tool Policy 中重复落实。
23. Session 中怎样观察 Skill 使用
自动匹配可以观察:
- Assistant 发出的
readTool Call; - 对应 SKILL.md Path;
- Tool Result 中的正文;
- 后续 Tool 是否符合工作流。
显式调用可以观察:
- User Message 已变成
<skill>Block + 参数; - 原始
/skill:name字符串通常不会作为最终展开文本进入模型; - 出错时的 Extension Error。
桌面 UI 可显示:
已选择 Skill:error-log-analysis
来源:project
加载方式:automatic read
工作流文件:/absolute/path/SKILL.md
这需要宿主从 Message/Event 推导,不是 Pi 的独立 skill_start Event。
24. 怎样提高触发可靠性
按优先级:
- 写清 Description 的 What 与 When;
- 避免多个 Skill Description 大面积重叠;
- 保证
readActive 且 Prompt 已重建; - 对用户明确选择提供
/skill:name; - 对产品固定流程,由宿主显式展开或用 Extension 编排;
- 用测试记录模型是否读取、读了哪份、后续是否遵循。
“再强调一句必须使用 Skill”不是唯一方案,先确认可见性和路由信息。
25. 一个完整例子
sequenceDiagram
participant U as 用户
participant P as System Prompt
participant M as 模型
participant R as read
participant B as bash
participant S as Session
U->>M: 分析 server.log 的重复超时
P-->>M: error-log-analysis 元数据
M->>R: read /.../SKILL.md
R-->>M: 本页工作流;必要时继续 offset
M->>R: 读取日志头尾
R-->>M: 原始证据
M->>B: 运行 summarize-log.mjs
B-->>M: 按文件统计
M->>R: 回读关键时间段
R-->>M: 关联证据
M-->>U: 观察、假设、置信度、下一步
M-->>S: Messages 与 Tool Results 正常持久化
这里没有隐藏的 Skill Runtime。每一步都能用之前章节的 Message、Tool、Event 和 Session 模型解释。
26. 常见误区
“Pi 用关键词搜索自动选 Skill”
错误。当前 Core 把目录交给模型判断。
“模型选中 Skill 后 Pi 自动运行整个目录”
错误。模型按正文逐步调用已有 Tool。
“显式调用会创建一个新的 System Message”
错误。它展开成普通 User Message 内容。
“引用文件会随 SKILL.md 自动加载”
错误。需要后续 Tool Call。
“disable-model-invocation 禁止用户调用”
错误。它只隐藏模型自动发现。
“读过一次后 Session 有永久 Active Skill”
错误。没有这个状态;正文只是 Context 中的消息内容。
“Skill 失败有统一 skill_error”
错误。读取、脚本、Tool Policy 和显式展开走各自错误通道。
27. 本章小结
- Skill 没有统一执行器,模型在普通 Agent Loop 中读取指令并调用 Tool;
- coding-agent 的自动匹配依赖 Loader、可见性、read Active、已重建 Base Prompt,以及 最终 Extension Prompt 仍保留目录;
- 通用 Harness 不自动插入 Skill 目录;宿主要在
systemPrompt回调中格式化目录,并提供 能读取 Skill 文件的 Tool; - 当前 Core 没有确定性的关键词、Embedding 或独立 Skill Router;
- 自动正文通过 Read Tool Result 进入 Transcript,大文件需要 Offset 分页;
- 通用 Harness 用已加载的
Skill.content显式调用,未知名称抛invalid_argument; - coding-agent
/skill:name在模型请求前同步读取并展开成 User Message; - 未知 Skill 或读取失败会保留原文本,后者另发 skill_expansion Error;
- disable-model-invocation 只影响自动目录;
- Skill 引用和脚本需要模型继续调用 Tool,并受宿主 Policy 约束;
- 多 Skill 的选择与指令冲突没有内置依赖/互斥系统;
- Skill 正文跨多轮存在于 Context,但没有永久 Active Flag;
- 桌面 UI 可从 Message/Event 推导 Skill 使用过程,而不是等待专用事件。
28. 自测
- 自动 Skill 匹配的五个前提是什么?
- 当前 Pi Core 是否有独立 Skill Router?
- 自动读取正文后,它以什么形式进入 Context?
/skill:name怎样变成模型输入?- 未知 Skill 和文件读取失败分别怎样处理?
disable-model-invocation影响哪条路径?- 两个不同名称 Skill 的指令冲突由谁处理?
- 为什么关键安全规则不能只写在 Skill Body 中?
- 通用 Harness 与 coding-agent 显式调用的正文来源和未知名称行为有何不同?
- 为什么只把 Skill 放进通用 Harness 的
resources.skills还不能自动匹配?