Pi Package 到底是什么:从 `pi install` 到模型真正“知道”一个 Subagent

最近在折腾 Pi 的 subagent。

一开始我对 Pi Package 的理解非常朴素:

pi install npm:my-subagent

那大概就是把一个插件安装到了:

~/.pi/agent/extensions/...

然后 Pi 启动的时候扫一下这个目录,于是模型就知道:

哦,我现在有一个 subagent。

后来仔细看了一遍 Pi 的 package、extension、skill 和 settings 机制,发现这个理解基本上把几个不同层次的东西揉到了一起。

真正有意思的问题其实不是:

pi install 把文件放哪了?

而是:

一个 npm package,是怎么一步一步变成 LLM 眼里的一个能力的?

这篇就把这条链路完整拆一下。


1. 先说一个反直觉的结论

执行:

pi install npm:my-subagent

之后,模型其实并不知道你安装了一个叫 my-subagent 的 package。

甚至可以说:

LLM 根本不需要知道 package 这种东西存在。

Package 是 Pi Host 层的概念。

LLM 最终看到的是另外一些东西:

Tools
Skills
System Prompt / Instructions
Context

所以整个问题实际上分成四层:

flowchart LR A["Package"] --> B["Resource"] B --> C["Runtime Capability"] C --> D["Model Context"] D --> E["LLM"]

理解 Pi Package,最重要的就是不要把这几层混在一起。


2. pi install 到底安装了什么?

我们先从最外层开始。

比如我写了一个 package:

my-subagent/
├── package.json
├── extensions/
│   └── subagent.ts
└── skills/
    └── delegation/
        └── SKILL.md

然后发布 npm:

npm publish

用户安装:

pi install npm:my-subagent

Pi Package 是一个可以一起分发多种资源的单元,包括:

extensions
skills
prompts
themes

它本质上仍然是一个普通目录或者 npm package。

这里第一个很容易产生的误区是:

Pi Package == Extension

其实不是。

更准确地说:

flowchart TD P["Pi Package"] P --> E1["Extension"] P --> E2["Extension"] P --> S1["Skill"] P --> S2["Skill"] P --> PR["Prompt"] P --> T["Theme"]

Package 更像是一个 distribution container。

Extension、Skill 等才是里面真正能够被 Pi 消费的 Resource。


3. 那全局 package 到底记录在哪里?

我们这篇只讨论 global package。

执行:

pi install npm:my-subagent

以后,Pi 会把这个 personal/global package declaration 写进:

~/.pi/agent/settings.json

概念上可以理解成:

{
  "packages": [
    "npm:my-subagent"
  ]
}

注意这里有一个很重要的变化。

以前我的脑子里是:

flowchart LR A["pi install"] --> B["复制 extension 文件"] B --> C["~/.pi/agent/extensions"]

但实际上,更准确的模型是:

flowchart LR A["pi install npm:my-subagent"] --> B["Materialize Package"] A --> C["Register Package Declaration"] C --> D["~/.pi/agent/settings.json"]

Pi 的 settings 里有 package declarations,同时 Pi 的 Resource System 又分别认识:

extensions
skills
prompts
themes

这已经暗示了一件事情:

Package 和 Resource 是两个层次。


4. Package 不需要被复制进 extensions/

比如:

my-subagent/
├── package.json
├── extensions/
│   └── subagent.ts
└── skills/
    └── delegation/
        └── SKILL.md

Pi 可以直接从 package 内部发现这些资源。

默认情况下,它认识这些 conventional directories:

extensions/
skills/
prompts/
themes/

也可以在 package.json 里面显式声明:

{
  "name": "my-subagent",
  "pi": {
    "extensions": [
      "./extensions/subagent.ts"
    ],
    "skills": [
      "./skills"
    ]
  }
}

所以:

~/.pi/agent/extensions/

和:

npm:my-subagent
  └── extensions/subagent.ts

其实是两种不同的 Resource 来源。

flowchart TD R["Pi Resource Discovery"] R --> L["Loose Resources"] R --> P["Packages"] L --> LE["~/.pi/agent/extensions"] L --> LS["~/.pi/agent/skills"] P --> PKG["npm:my-subagent"] PKG --> E["extensions/"] PKG --> S["skills/"] PKG --> PR["prompts/"] PKG --> T["themes/"]

所以 package 安装以后,你跑:

ls ~/.pi/agent/extensions

没看到 my-subagent,完全正常。

它根本没有必要出现在这里。


5. 真正关键的问题来了

到这里其实只解决了:

Pi 在磁盘和配置层面怎么知道这个 package。

但这不是我们真正关心的问题。

真正有意思的是:

我新开一个 Pi session,它怎么知道我以前装过这个 package?

以及更进一步:

LLM 又是怎么知道自己有一个 subagent 可以调用的?

这两个问题其实还不是同一个问题。


6. 每次 Pi 启动,它都会重新建立 Resource 世界

假设现在:

~/.pi/agent/settings.json

里面已经有:

npm:my-subagent

我随便找一个项目:

cd ~/projects/foo
pi

新的 Pi process 启动。

这时候可以把启动过程抽象成:

flowchart TD A["Pi starts"] --> B["Read Global Settings"] B --> C["~/.pi/agent/settings.json"] C --> D["packages: npm:my-subagent"] D --> E["Resolve Package"] E --> F["Discover Package Resources"] F --> G["extensions/subagent.ts"] F --> H["skills/delegation/SKILL.md"]

也就是说:

Package 并不是写进某个 session 的。

它属于 user/global configuration。

每一个新的 Pi runtime 都会从这个全局配置重新建立自己的 Resource 集合。

所以不是:

install package
     │
     ├── modify session A
     ├── modify session B
     └── modify session C

而是:

flowchart TD S["~/.pi/agent/settings.json"] --> P["Global Package Declaration"] P --> A["Pi Process A"] P --> B["Pi Process B"] P --> C["Pi Process C"] A --> DA["Resource Discovery"] B --> DB["Resource Discovery"] C --> DC["Resource Discovery"] DA --> SA["Session A"] DB --> SB["Session B"] DC --> SC["Session C"]

这就是为什么一个 global package 会自然出现在之后启动的各个 Pi 会话里。


7. 但是,到这里 LLM 仍然不知道它存在

这是整个问题最容易漏掉的一层。

到目前为止发生的一切:

settings.json
package resolution
resource discovery
extension loading

都发生在:

Pi Host / Runtime

这一侧。

LLM 不会自己:

ls ~/.pi/agent

也不会:

cat package.json

更不会主动扫描:

node_modules

所以:

Pi runtime 知道 package

并不等于:

LLM 知道 package

中间还差了一座桥。


8. 这座桥就是 Extension 注册出来的能力

假设我们的:

extensions/subagent.ts

大概做这么一件事:

export default function (pi) {
  pi.registerTool({
    name: "subagent",

    description:
      "Delegate a task to a specialized subagent",

    parameters: {
      // ...
    },

    execute: async (...) => {
      // spawn / run subagent
    }
  })
}

Pi 加载这个 extension。

extension 执行:

pi.registerTool("subagent")

于是当前 Pi runtime 原本可能有:

read
bash
edit
write

现在变成:

read
bash
edit
write
subagent

这里发生了一次非常关键的语义转换:

flowchart LR A["npm:my-subagent"] --> B["Pi Package"] B --> C["subagent.ts"] C --> D["pi.registerTool()"] D --> E["Runtime Tool: subagent"]

到了这里,npm package 已经不重要了。

现在 Pi Runtime 里真正存在的是:

Tool: subagent

这才是真正能够继续进入 Model 世界的东西。


9. LLM 最终看到的是什么?

假设 Pi 下一次向模型发请求。

模型收到的东西,可以粗略理解成:

System Prompt
Conversation
Available Tools

其中 Available Tools 现在包含:

read(...)
bash(...)
edit(...)
write(...)

subagent({
    task: string,
    ...
})

并且带有 description:

Delegate a task to a specialized subagent.

于是模型终于知道:

我有一个叫 subagent 的工具。

注意它知道的不是:

用户安装了 npm package my-subagent。

而是:

当前环境提供了一个 subagent tool。

这两个说法看起来很接近,实际上是完全不同的抽象层。

flowchart LR subgraph Host["Pi Host / Runtime"] A["Package"] --> B["Extension"] B --> C["Tool Registry"] end subgraph Model["Model World"] D["Tool Definition"] E["LLM"] end C --> D D --> E

这个边界非常重要。


10. 所以 Package 本身对 LLM 是透明的

站在模型视角:

npm package name?
不知道。

安装路径?
不知道。

是不是从 GitHub clone 的?
不知道。

是不是 ~/.pi/agent/extensions 里的?
不知道。

是谁 install 的?
也不重要。

模型真正关心的是:

What tools do I have?

What instructions do I have?

What skills are available?

When should I use them?

因此:

flowchart LR A["npm:my-subagent"] --> B["Pi Package"] B --> C["Extension Resource"] C --> D["registerTool()"] D --> E["Runtime Tool Registry"] E --> F["Tool Definition"] F --> G["LLM"]

到了最后,最开始那个 npm package 已经完全消失在模型的抽象世界里了。


11. Skill 又是什么?

这里还有另外一个很容易混淆的问题。

既然 extension 已经给模型提供:

subagent(...)

为什么很多 package 还要提供:

SKILL.md

因为:

能调用,和知道怎么调用,是两件事。

比如 extension 给模型的是:

subagent(task)

这是 Capability。

但什么时候应该 delegate?

什么任务适合 subagent?

subagent 返回结果以后应该怎么办?

什么时候不应该创建 subagent?

这些更像:

Policy / Knowledge / Workflow

这就是 Skill 更适合解决的问题。

flowchart TD P["my-subagent Package"] P --> E["Extension"] P --> S["Skill"] E --> C["Capability
你能做什么"] S --> K["Knowledge / Workflow
什么时候、怎么做"] C --> L["LLM"] K --> L

所以一个比较完整的 subagent package,其实同时解决两个问题:

Extension 让 Agent 有手。

Skill 告诉 Agent 什么时候伸手、怎么伸手。

这比“package 给模型增加了一个功能”要准确得多。


12. Skill 也不是简单粗暴地全部塞进 Context

还有一个细节很值得注意。

Skill 的发现和 Skill 全文进入 Context,也不是完全同一件事情。

一个 Skill 可以先以类似:

name
description
location

这样的发现信息出现在 available skills 中,然后模型真正需要时再读取完整内容。

这背后的思想其实非常合理。

假设我有 100 个 Skill:

Kubernetes
PostgreSQL
React
Security Review
Code Review
Subagent Delegation
...

如果每次请求都:

把 100 个 SKILL.md 全文
塞进 system prompt

Context 很快就炸了。

更合理的思路是:

先告诉模型“这些能力存在”。

真正需要的时候,再加载具体说明。

于是可以继续把它拆成:

flowchart LR A["Package Discovery"] --> B["Skill Discovery"] B --> C["Skill Metadata Awareness"] C --> D{"当前任务需要?"} D -->|Yes| E["Load Skill Content"] D -->|No| F["不占用完整 Skill Context"] E --> G["LLM 使用 Skill"]

这里其实已经有一点 progressive disclosure 的味道了。


13. 所以一个 Subagent Package 实际有两条进入模型的路径

现在整个东西终于可以拼起来了。

假设:

my-subagent/
├── package.json
├── extensions/
│   └── subagent.ts
└── skills/
    └── delegation/
        └── SKILL.md

安装:

pi install npm:my-subagent

之后真正发生的是:

flowchart TD A["npm:my-subagent"] --> B["Pi Package"] B --> C["Resource Discovery"] C --> E["extensions/subagent.ts"] C --> S["skills/delegation/SKILL.md"] E --> EL["Extension Loading"] EL --> RT["registerTool()"] RT --> TOOL["Runtime Tool"] S --> SD["Skill Discovery"] SD --> INFO["Available Skill Info"] INFO --> CONTENT["按需加载 Skill Content"] TOOL --> LLM["LLM"] CONTENT --> LLM LLM --> CAP["我可以调用 subagent"] LLM --> KNOW["我知道什么时候、怎么调用"]

这才是一个比较完整的 subagent package。

这里尤其值得记住的是:

Extension → Capability
Skill     → Guidance

两条路径最后才在模型这一层汇合。


14. 为什么已经打开的 Session 不一定马上认识新 Package?

顺着这个模型,还能解释一个非常实际的问题。

假设 Terminal A 已经:

pi

跑起来了。

然后 Terminal B:

pi install npm:my-subagent

磁盘上的:

~/.pi/agent/settings.json

已经变了。

但 Terminal A 里的 LLM 不一定突然就知道:

subagent

为什么?

因为:

磁盘配置发生变化

和:

当前 Runtime 的 Resource / Tool Registry 发生变化

不是一回事。

可以把它画成:

flowchart LR A["settings.json changed"] -. "不会自动等价于" .-> B["Existing Runtime Changed"]

需要重新加载 Resource:

flowchart LR A["settings.json changed"] --> B["/reload 或重启"] B --> C["Resource Discovery"] C --> D["Extension Loading"] D --> E["Tool Registration"] E --> F["Updated Tool Registry"] F --> G["下一次 LLM Request"]

于是下一次模型请求,它才能看到新的能力。


15. pi install 真正做的事情

所以回头再看:

pi install npm:my-subagent

我现在不会把它简单理解成:

安装一个插件。

而更愿意理解成:

向 Pi 的全局 Resource System 注册并 materialize 一个新的 Package Source。

这个 Package Source 可以暴露:

extensions
skills
prompts
themes

然后 Pi Runtime 在启动或者 reload 时,把这些 Resource 转换成:

Runtime Capability
+
Model-visible Context

整个生命周期可以压缩成一张图:

flowchart TD subgraph Install["Install Time"] NPM["npm registry"] INSTALL["pi install npm:my-subagent"] MAT["Materialize Package"] SETTINGS["~/.pi/agent/settings.json"] NPM --> INSTALL INSTALL --> MAT INSTALL --> SETTINGS end subgraph Runtime["Runtime"] START["Pi starts / reload"] READ["Read Global Settings"] RESOLVE["Resolve Packages"] DISCOVER["Discover Resources"] EXT["Extension"] SKILL["Skill"] REG["Register Runtime Capability"] GUIDE["Skill Discovery / Guidance"] CONTEXT["Build Model-visible Context"] LLM["LLM"] START --> READ READ --> RESOLVE RESOLVE --> DISCOVER DISCOVER --> EXT DISCOVER --> SKILL EXT --> REG SKILL --> GUIDE REG --> CONTEXT GUIDE --> CONTEXT CONTEXT --> LLM end SETTINGS --> START MAT --> RESOLVE

这张图基本就是整篇文章。


16. Package、Extension、Skill,其实属于三个不同问题

最后可以把这三个概念压缩成三句话。

Package:东西怎么分发?

它解决的是:

Distribution
Dependency
Version
Resource Packaging

来源可以是:

npm
git
local path

Extension:Pi 怎么获得新的运行时能力?

比如:

tool
command
event handler
UI
runtime behavior

它解决的是:

Capability

Skill:模型怎么知道什么时候、如何使用能力?

它解决的是:

Knowledge
Workflow
Instructions

所以最终可以压成:

flowchart LR P["Package"] -->|"怎么送进来"| D["Distribution"] E["Extension"] -->|"能做什么"| C["Capability"] S["Skill"] -->|"什么时候、怎么做"| K["Knowledge / Workflow"]

这三个东西放在一起,才组成一个比较完整的 Agent Extension System。


17. 最终的心智模型

如果整篇文章只记住一张图,我觉得记这个就够了:

flowchart TD subgraph Distribution["① Distribution"] SOURCE["npm / git / local path"] PACKAGE["Pi Package"] SOURCE --> PACKAGE end subgraph Discovery["② Resource Discovery"] EXT["Extension"] SKILL["Skill"] PROMPT["Prompt"] THEME["Theme"] end subgraph Runtime["③ Pi Runtime"] TOOLS["Tool Registry"] SKILLS["Skill Registry / Metadata"] end subgraph Context["④ Model Context"] TOOLDEF["Tool Definitions"] INSTRUCTIONS["Instructions / Skill Content"] LLM["LLM"] end PACKAGE --> EXT PACKAGE --> SKILL PACKAGE --> PROMPT PACKAGE --> THEME EXT --> TOOLS SKILL --> SKILLS TOOLS --> TOOLDEF SKILLS --> INSTRUCTIONS TOOLDEF --> LLM INSTRUCTIONS --> LLM

所以,“安装了一个 subagent package,为什么之后每个 Pi session 都知道它?”

现在可以给出一个更准确的回答:

它其实不知道你“安装了一个 package”。

pi install 只是把 package 加入全局配置;之后每个 Pi runtime 启动时,从 ~/.pi/agent/settings.json 发现这个 package,再发现其中的 extension / skill 等 Resource。

Extension 注册运行时能力,Skill 提供使用这些能力所需的信息;最终 Pi 再把这些东西投影成模型可见的 Tools 和 Context。

模型认识的从来不是 Package。

模型认识的是 Package 最终投影到 Runtime 和 Context 中的能力。

我觉得这是理解 Pi Package 最重要的一点。

Package 是给 Pi 看的。

Tool 和 Skill,才是给 Agent 看的。

而真正有趣的地方,也正好发生在这两者之间。

发表评论: