Site logo
Published on

Pi 源码解读(六):Skill 和 Extension 分别怎么影响执行?

Authors
  • avatar
    Name
    Stone
    Twitter
本篇目录

前五课讲了 Pi 怎样执行任务、调用工具和整理上下文。现在试着给它加一点团队习惯:按我们的 Vue 规范检查代码,并拦住不应该发生的文件修改。

NOTE

Skill 更像工作说明书;Extension 是接入 Pi 运行过程的程序。

“告诉模型应该怎么做”和“在执行前用程序检查”,不是同一件事。

源码基准:Pi v0.84.1。本文是静态源码分析,例子为教学推演;Claude Code 只在对照段出现。

一、一个需求,其实包含两类问题

假设团队要求:

检查 App.vue 时,先说明问题,再给出最小修改建议;不要顺手修改 package-lock.json。

“先说明问题、再给建议”是一套工作方法,适合写进 Skill。假如要在某次修改发生前直接阻止它,就需要执行阶段的程序逻辑,Pi 的 Extension 可以参与这一环节。Skill 可以包含辅助脚本,但说明书本身不会自动变成一个工具执行钩子。12

先把三个词分开:

概念主要解决什么本例中的用途
Skill把工作方法、说明和辅助资料提供给模型。说明怎样审查 Vue 组件。
Tool提供真正可执行的能力。read 读取文件,edit 修改文件。
Extension用程序扩展 Pi,包括注册工具、命令和事件处理器。在工具执行前检查这次修改是否应当阻止。

工具与扩展不是互斥的分类:一个 Extension 可以注册一个新 Tool,也可以不新增工具,只参与已有工具的执行过程。3

二、Pi 怎么让模型知道有这份 Skill?

假设我们准备了一个名叫 vue-review 的 Skill,里面写着检查顺序、注意事项,以及参考资料的位置。

Pi 不需要一开始就把所有 Skill 的完整正文塞给模型。正常发现流程会先提取名称和描述,把可用 Skill 的介绍放进系统提示;需要某项技能时,再让模型使用 read 读取完整的 SKILL.md。官方文档也提醒:模型不一定主动读取,可以通过明确提示或启用后的 /skill:名字 命令加载。4

这像先给人看目录,决定需要哪一章,再打开正文。

在源码里,resource-loader.ts 的 updateSkillsFromPaths 会调用 loadSkills 收集技能;随后,构建系统提示的代码再把技能信息组织进去。资源已经被发现,不等于完整正文已经进入这一轮模型输入。56

因此,“装了 Skill,模型怎么没按规范做?”要分两步排查:它有没有看到这份技能的介绍,实际执行时有没有加载并遵循正文。不能只检查磁盘上有没有文件。

三、Extension 在哪里把修改拦下来?

假设模型提出:使用 edit 修改 package-lock.json。我们希望自定义扩展拒绝这次请求。这是教学中准备增加的规则,不是说 Pi 默认禁止修改这个文件。

第三课讲过“找到工具、校验参数、执行函数”。这次把中间补完整:Pi 会在执行函数之前调用 beforeToolCall。在 Coding Agent 层,AgentSession 把这个入口连接到扩展的 tool_call 事件。7

主要思路只有下面这几步:

收到工具请求:
    找到工具,准备并校验参数
    把请求交给执行前的扩展处理器
    如果处理器要求阻止:
        不执行工具,返回错误结果和原因
    否则:执行工具,返回真实结果

Pi 的 ExtensionRunner.emitToolCall 会依次调用已注册的处理器;遇到返回 block: true 的结果就停止继续检查,并将阻止结果交回调用方。Agent Loop 据此生成错误结果,不进入工具的 execute。89

这不是模型“听话了”,而是运行时没有执行这次操作。模型随后能够看到拒绝原因,再决定如何继续。

还有一个值得注意的分支:在这条执行前钩子链路里,如果处理器抛出异常,错误会向上传递并转成工具错误,而不是忽略异常后继续执行。这里只描述这条链路,不能推断 Pi 的所有事件都使用同样策略。109

四、能拦截,不代表已经有了完整安全边界

假如扩展只检查 edit,那么换成 write 或通过命令修改文件,是否也被覆盖?不能凭刚才那条规则保证。

这是从检查范围得出的工程要求:保护一个文件,要考虑所有可能修改它的入口,而不只是一个工具名字。 路径别名、符号链接和扩展自身的操作,也不能靠一句“文件名相同就拒绝”全部解决。

此外,Pi 官方文档明确说明,Extension 以宿主进程的系统权限运行,可以执行代码;它不是天然被隔离起来的安全程序。因此,只应加载可信扩展。11

WARNING

Skill 提供说明,钩子检查经过它的操作,沙箱限制执行环境。

三者可以配合,但不能互相冒充。尤其不要把一个简单拦截示例当成完整的文件权限系统。

五、对照 Claude Code:比较同一个检查位置

Claude Code 官方文档公开了 PreToolUse:在工具使用前介入,并可以返回拒绝或要求确认等决定。它与 Pi 的执行前钩子适合放在一起理解:两者都提供在动作发生前参与决策的位置。12

但事件名称、返回格式和具体行为不能照搬。本课对 Pi 的结论来自源码;对 Claude Code 的这段说明来自其官方公开文档,不表示已读过 Claude Code 的内部循环实现。对照资料查阅于 2026-09-25。

六、源码和面试,各抓住一条线

先跟清楚:工具请求 → 执行前钩子 → 扩展处理器 → 放行或阻止。不必背 Extension API 的全部方法。

需要定位时,再展开源码入口

resource-loader.ts 的 updateSkillsFromPaths:确认 Skill 怎样被收集。

agent-session.ts 的 _installAgentToolHooks:确认通用 Agent 钩子如何连接扩展事件。

extensions/runner.ts 的 emitToolCall:确认处理器的调用顺序与阻止结果。

回到 agent-loop.ts 的 prepareToolCall:确认被阻止后确实不会进入执行函数。

面试时,可以这样讲

对照源码看过之后,再用自己的话表达:

我看了 Pi 的 Skill 和 Extension 两条扩展路径。Skill 主要把工作方法按需提供给模型,Extension 则能用代码接入运行过程。我跟过执行前的链路:AgentSession 将 beforeToolCall 接到扩展处理器,处理器返回阻止结果时,这次工具不会执行。不过钩子只能覆盖经过它的操作,不能直接等同于操作系统级的沙箱。

一个不用写代码的小练习

Skill 里写了“不要修改锁文件”,扩展又只拦截 edit。能否保证锁文件绝对不会被修改?

想过之后再看答案

不能。Skill 是提供给模型的指导;只拦截 edit,又没有覆盖其他修改途径。需要先明确威胁与权限边界,再决定限制哪些工具、如何检查路径,以及是否需要隔离执行环境。


这一课记住:说明书影响模型怎么想,执行前钩子影响这次操作能否发生。

参考源码

Footnotes

  1. Pi v0.84.1 · packages/coding-agent/docs/skills.md ↩

  2. Pi v0.84.1 · packages/coding-agent/docs/extensions.md ↩

  3. Pi v0.84.1 · packages/coding-agent/docs/extensions.md ↩

  4. Pi v0.84.1 · packages/coding-agent/docs/skills.md ↩

  5. Pi v0.84.1 · packages/coding-agent/src/core/resource-loader.ts,第 622–643 行 ↩

  6. Pi v0.84.1 · packages/coding-agent/src/core/system-prompt.ts ↩

  7. Pi v0.84.1 · packages/coding-agent/src/core/agent-session.ts,第 444–470 行 ↩

  8. Pi v0.84.1 · packages/coding-agent/src/core/extensions/runner.ts,第 879–899 行 ↩

  9. Pi v0.84.1 · packages/agent/src/agent-loop.ts,第 562–628 行 ↩ ↩2

  10. Pi v0.84.1 · packages/coding-agent/src/core/agent-session.ts,第 452–470 行 ↩

  11. Pi v0.84.1 · packages/coding-agent/docs/extensions.md ↩

  12. Claude Code 官方文档 · PreToolUse 决策控制 ↩