pi-neo-subagents0.1.10
Subagents for Pi: define Markdown agent definitions and delegate tasks to isolated nested agent sessions
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
pi-neo-subagents
English · 简体中文
为 Pi 提供子代理:把专业化的代理写成 Markdown 定义文件,主代理就能把一件自洽的任务委派给它——你也可以自己直接运行。 每次委派都跑在独立的嵌套会话里,有自己的对话历史,所以主上下文始终小而聚焦。
pi install npm:pi-neo-subagents
- 委派:主代理调用
task工具,支持单个 / 并发(fan-out)/ 链式(chain), 可设置单次预算,并有运行台账。 - 定义:Markdown 定义文件,支持工具白名单、模型覆盖、可选开启的受控递归; 项目级优先,定义随仓库走、可 review、可提交。
- 直接运行:
/subagents list|run|new|edit|rm|stats,不经过模型也能自己跑。
安装
从 npm 安装:
pi install npm:pi-neo-subagents # 用户级
pi install npm:pi-neo-subagents -l # 项目级(需要授予项目信任)
从目录或单个文件安装(TypeScript,无需构建):
pi install /path/to/pi-neo-subagents
pi --extension /path/to/pi-neo-subagents/index.ts
项目级安装会写进 .pi/settings.json,而 Pi 只在授予项目信任之后才会采用它——
所以要么用 --approve 启动 Pi,要么在交互中信任该项目,否则这个包看起来像被忽略。
放进 ~/.pi/agent/extensions/ 或 <project>/.pi/extensions/ 也能被自动发现。
如果要发布到 npm:从 package.json 里删掉 "private": true,并加上
pi-package 关键词;pi.extensions 清单已经声明好了。
代理定义
定义是带 YAML 风格 frontmatter 的 Markdown 文件,放在两个资源目录之一:
- 项目级:
.pi/agents/*.md(同名冲突时优先,列表里会标注来源)。 发现逻辑会从当前工作目录向上找最近的.pi/agents,所以在子目录里启动 Pi 依然能找到仓库的代理定义。 - 用户级:
~/.pi/agent/agents/*.md
---
description: Reviews code changes
tools: read, grep
model: anthropic/claude-sonnet-4-5
systemPrompt: replace
---
You are a careful code reviewer. Always cite the file you reviewed.
| 字段 | 含义 | 默认值 |
|---|---|---|
name |
规范的代理名 | 文件名(不含扩展名) |
description |
一行描述,用于委派时的发现 | 必填 |
tools |
收窄子代理的工具集(省略 = 内置 + 宿主扩展) | 内置 + 宿主扩展 |
model |
运行所用模型(provider/id 或裸 id) |
继承宿主会话的模型 |
systemPrompt |
replace 表示正文就是完整系统提示词 |
追加到 Pi 的默认提示词之后 |
maxTurns |
单次委派的 assistant 轮数上限 | subagents.defaultMaxTurns(50) |
timeoutMs |
墙钟时间上限(毫秒) | subagents.defaultTimeoutMs(10 分钟) |
maxTokens |
单次委派的总 token 上限 | 不限制 |
thinking |
思考级别(minimal、low、medium、high、xhigh、max) |
继承 |
allowSubagents |
true 允许该代理再用 task 工具继续委派 |
false |
frontmatter 同时接受 tools: read, grep 和 YAML 块列表两种写法:
tools:
- read
- grep
支持带引号的值和行尾 # 注释。定义写错会直接报错,而不是悄悄改变行为:
tools:存在但为空 → 报错(以前它表示"全部默认工具")- 引号未闭合 → 报错
systemPrompt取了replace以外的值 → 报错(不写这个字段仍然表示追加)- 创建定义时:未注册的工具名、无法解析的模型、非法预算,或试图使用
task工具(子代理永远不能再委派)→ 报错 - 同一目录下两个定义用了相同的
name→ 后一个文件被拒绝,并报重名错误
Markdown 正文就是这个代理的专门指令。
创建定义
定义文件既可以手写,也可以让模型来写。create_subagent 工具接收
{ name, description, instructions, tools?, model?, scope?, overwrite? },
文件格式和存放位置都由它负责:
scope: "project"(默认) →<cwd>/.pi/agents/<name>.md,定义随仓库走, 可以被 review 和提交。scope: "user"→~/.pi/agent/agents/<name>.md,仅在你明确要求时使用。- 已有定义不会被覆盖,除非
overwrite: true;会逃出 agents 目录的名字被拒绝; 生成的文件在落盘前会先做校验。 - 新建的定义在同一个会话里立刻就能被委派。
所以在一个没有任何定义的项目里说"一个子代理审待提交的 diff,一个扫一遍仓库的
TODO",产出的是项目内的 reviewer.md 和 scout.md,而不是写进你的全局配置。
预算
一次委派可以按轮数、墙钟时间、token 设上限——写在定义里,或者按调用传入 (按调用传入的值优先):
// task 工具调用
{ "agent": "scout", "task": "…", "maxTurns": 3, "timeoutMs": 60000, "thinking": "low" }
每次委派还有一层兜底默认值——定义和调用都没设上限时才生效:
subagents.defaultMaxTurns(50)和 subagents.defaultTimeoutMs(10 分钟)。
把其中任意一个设为 0 即在全局关闭该上限;定义里或按调用显式传入的值永远优先。
这样模型跑飞或命令挂起(宿主 bash 不带显式 timeout 时没有看护)都不会把并发
派发无限期堵死——运行会被腰斩并如实上报,而不是卡住。
触到上限是一类正常结果,不是失败:子代理已经产生的内容会原样返回,并附一句
指明是哪个预算被触发的提示,例如
[Budget stop: turns limit 1 reached (observed 1). Partial reply above.]。
于是"被腰斩"和"真失败"始终可区分,用量信息也照常汇报花了多少。
从主代理委派
task 工具的描述里会列出所有可用的代理、两个定义目录,并说明项目级定义优先。
主代理派活时必须写自洽的任务描述——子代理只看得到任务文本,永远看不到宿主
对话。子代理的最终回复作为工具结果返回(过长的回复在 30,000 字符处截断,完整
内容写入临时文件)。
子代理的系统提示刻意从简:定义正文 + Pi 核心规则——不带技能、主题、提示词模板, 也不带 Pi 文档指针;需要这些知识就写进任务文本。
并发(fan-out)
一次调用可以同时派发多个任务:
{ "tasks": [
{ "agent": "reviewer", "task": "Review the diff of src/ for bugs" },
{ "agent": "scout", "task": "Find every TODO under src/", "maxTurns": 4 }
] }
- 结果按提交顺序返回,每个任务一个区块,带状态(
ok、budget stop (…)、failed)和详情。 - 一个任务失败不会取消其它任务;只有全部失败时整个调用才算错误。
- 默认并发上限为 4,可用
PI_NEO_SUBAGENTS_MAX_CONCURRENCY=<n>调整。 - 取消这次调用会取消所有在飞任务。
- 单个
{ agent, task }调用的行为与以前完全一致。 - 在 TUI 中会有一个 widget 列出每个在飞委派当前的轮次和工具,派发结束后清空; 其它模式不会显示。
一次性任务(不写定义)
对于不会复用的工作,条目可以直接带内联指令而不写代理名:
{ "instructions": "You read one file and summarize it in three bullets.",
"tools": ["read"],
"task": "Summarize docs/specs/subagents.md" }
- 不落盘,
/subagents里也看不到;这次运行仍会计入/subagents stats,身份是 固定标签ad-hoc。 - 内联条目支持与定义相同的
model、预算和tools选项,可用于单个、并发和 链式派发,但永远不能再派生下级子代理。
宿主扩展工具
子代理永远不加载宿主扩展——正是这个隔离保证了 task 工具绝不会进入嵌套会话。
但你其他扩展提供的工具(搜索插件、浏览器、MCP 工具)会默认跨入子代理:
调用执行的是宿主实现,携带宿主会话自己的上下文(包括它的对话框与权限行为)。
tools列表严格收窄——只有点名的工具进子代理,既可裁剪也可把关扩展工具。- 内置工具无需特殊处理,原生可用。
task和create_subagent即使点名也绝不跨入。- 白名单里点了子代理给不了的工具,委派直接失败并点名,绝不静默丢弃。
/subagents run无法桥接(命令上下文接触不到宿主工具):需要桥接工具的定义 在该命令下会以同样信息失败——请改用task工具委派。
子代理会继承什么
上面的隔离针对的是宿主的私有机制。子代理确实会继承:
- 项目上下文文件(
AGENTS.md等,取自工作目录)。在本仓库干活的子代理就该 遵守本仓库的规矩;代价是子代理的每一轮都要为这份文件付 token,请保持精简。 - 除此之外什么都没有:宿主的对话历史、技能索引、提示词模板、主题、被发现的扩展, 以及 Pi 自己的 docs 段,全部隔离(技能与提示词属于宿主私有清单;docs 段对子代理 没有用途,却要每轮 343 个 token)。
受控递归
默认情况下子代理不能再委派。定义可以显式开启:
---
description: Plans work and delegates the parts
allowSubagents: true
tools: read, task
---
开启后,该代理的嵌套会话里会拿到 task 工具,同时有深度上限约束整棵树
(subagents.maxDepth,默认 2——顶层委派记为深度 1)。超限时该次调用失败,并
报告上限与实际尝试的深度。每个结果都带 details.depth,台账里也会显示每个代理
跑到过的最深层级。
链式(chain)
步骤可以按顺序执行,每一步能消费上一步的回复——比如"先审后修"的流水线:
{ "chain": [
{ "agent": "reviewer", "task": "Review src/runner.ts and list the problems" },
{ "agent": "fixer", "task": "Fix exactly these problems, nothing else: {previous}" }
] }
{previous}会被替换成上一步的回复;在第一步里使用它会在任何任务开跑前被拒绝。- 链在第一个失败或被预算腰斩的步骤处停止;剩余步骤标记为
skipped,结果里 会写明每一步的状态。 - 链是严格串行的,所以并发上限对它不适用。
默认委派只有一层:子代理自身永远拿不到 task 工具。嵌套会话也不会加载任何扩展、
技能、提示词模板或主题,所以子代理的提示词只有"它的定义 + 任务",绝不会包含宿主的
技能索引。
直接运行
/subagents # 列出全部定义
/subagents <name> # 查看某个定义
/subagents run <name> <task> # 跑一次;结果只给你看
/subagents run --to-context <name> <task> # 同时把结果送进模型上下文
/subagents run "code review" <task> # 名字含空格时用引号
/subagents run -- <name> <task> # -- 之后的内容原样当作字面量
/subagents new <name> [description] [--force] # 在解析到的项目目录里搭一个定义骨架
/subagents edit <name> # 打开对应文件;非法编辑不会保存
/subagents rm <name> [--yes] # 确认后删除(脚本里用 --yes)
/subagents stats # 每个代理的运行数、轮数、token、成本、平均耗时
/subagents stats reset # 清空本次会话的台账
台账按会话统计,覆盖每一次委派——单个、并发、链式的每一步——成功、失败、预算 中止分别计数:
Delegation ledger (this session):
- reviewer: 1 run (1 ok, 0 failed, 0 budget stops) · 3 turns · tokens 647 in / 410 out · $0.000718 · avg 4616ms
- scout: 1 run (1 ok, 0 failed, 0 budget stops) · 2 turns · tokens 421 in / 221 out · $0.000411 · avg 2475ms
Total: 2 runs · $0.001130
持久化与回放
这里不维护任何自己的存储,两件事都交给 pi:
- 台账是会话日志上的一个视图。 每次委派往会话文件追加一条自定义条目
(
pi-neo-subagents/run),stats只是对这些条目做一次折叠。恢复会话后数字 仍在——没有副文件、没有 schema 迁移、没有轮转策略。stats reset追加的是 墓碑而非改写历史,所以"清空"这件事在恢复后同样成立。 - 一次委派的完整过程就是一个子会话。 宿主会话有文件时,嵌套会话也会有:
同一个会话目录、名字形如
↳ <代理> · <日期> <时间>,并把宿主记作它的parentSession。于是回放就是 pi 自己的会话浏览器——恢复那个会话,每一轮和 每次工具调用都在。宿主会话没有文件时(内存会话)什么都不写:持久化跟随宿主 自己的持久化,因此不需要任何开关。
台账仍以当前会话为范围;子会话是普通的会话文件,留着或删掉都随你。
选项只在名字之前解析,所以任务文本里出现的 --to-context 仍属于任务内容。
未知选项会被拒绝并打印用法。
直接运行会报告一行用量摘要(模型、轮数、token、成本)。过长的结果在显示时会 缩短,完整内容写入临时文件,文件名在提示里给出。
输出通道
| 模式 | 命令输出去哪 |
|---|---|
| TUI | 通知 |
| RPC | extension_ui_request 通知 |
--print |
stdout |
--mode json |
stderr(stdout 是 JSON 事件流) |
长时间的直接运行会显示一行状态(<agent> · turn N),结束时清掉。
Windows / Git Bash
MSYS 会改写以 / 开头的参数,所以在命令行里传斜杠命令(pi -p "/subagents")
到 Pi 那里会变成 D:/Program Files/Git/subagents。换个引号方式,或者对这一个
参数关闭改写:
MSYS2_ARG_CONV_EXCL='/subagents' pi -p "/subagents"
pi -p "//subagents" # MSYS 会吃掉一个斜杠
这不影响在 Pi TUI 里输入命令,也不影响 RPC 客户端、cmd.exe 或 PowerShell。
设置
可选设置放在 Pi 设置的 subagents 键下(全局 ~/.pi/agent/settings.json 或项目
.pi/settings.json):
{
"subagents": {
"confirmProjectAgents": "ask",
"allowModelCreatedDefinitions": true,
"allowUserScopeCreation": false,
"maxDepth": 2,
"defaultMaxTurns": 50,
"defaultTimeoutMs": 600000
}
}
| 设置 | 含义 | 默认值 |
|---|---|---|
confirmProjectAgents |
"ask"、"allow" 或 "deny":项目级代理在会话中首次使用前是否需要确认 |
"ask" |
allowModelCreatedDefinitions |
是否允许模型用 create_subagent 写定义 |
true |
allowUserScopeCreation |
是否允许把定义写进用户级目录 | false |
maxDepth |
委派深度上限 | 2 |
defaultMaxTurns |
定义和调用都没设时的兜底轮数上限(0 = 不限) |
50 |
defaultTimeoutMs |
定义和调用都没设时的兜底墙钟上限(毫秒,0 = 不限) |
600000 |
确认结果在会话内按代理记忆,拒绝则不执行任何东西。用户级代理和内联(ad-hoc)
条目从不弹确认。在没有对话框 UI 的会话里,"ask" 会直接放行(自动化不会被卡住),
而 "deny" 依然拒绝。
开发
需要 Node 22.19 或更新版本——这是 Pi 宿主 SDK 声明的下限。
npm ci # 按提交的 lockfile 安装
npm test # vitest,全 hermetic:脚本化假模型,不联网
npm run typecheck
npm run test:live # live 冒烟套件(真实 provider,真实花费)
npm test 完全离线,从不需要凭证。live 套件被排除在它之外,只能通过
npm run test:live 运行(该脚本用 PI_NEO_SUBAGENTS_LIVE=1 开启):
- 它使用 Pi 已有的凭证,必要时先跑
pi auth。 PI_NEO_SUBAGENTS_LIVE_MODEL=provider/model选择模型(默认deepseek/deepseek-flash)。- 模型或 provider 缺失会大声失败并给出指引,而不是跳过——所以跑绿就代表真路径 确实走通了。
- 一次完整运行约 25 秒、几千 token:在临时项目里创建两个定义、并发派发给它们、 检查文件与台账,再委派一个 ad-hoc 任务以证明没有生成任何定义文件。
测试通过 Pi SDK 驱动完整的内存内代理会话,用 pi-ai 的 fauxProvider 作为脚本化
模型;断言只发生在会话边界(工具结果、可见消息、记录下来的 UI 输出)。
仓库结构:
| 路径 | 内容 |
|---|---|
index.ts、src/ |
扩展本体——所有会发布到 npm 的内容 |
test/ |
hermetic 测试套件;test/live/ 是需显式开启的 live 套件 |
docs/specs/ |
行为规格,含现行与已被取代的版本 |
docs/history/ |
设计记录:各版本对应的票据,以及真实模型测试报告 |
scripts/manual-test/ |
RPC 驱动与夹具,用于对真实 pi 进程做脚本化端到端运行 |
RELEASING.md |
版本如何切分、验证与发布 |