omp packages

@chthub/pi-subagents0.4.1

A minimal, complete sub-agent framework for pi: three tools, isolated contexts, parallel execution, automatic results, resumable sessions, and native UI.

GitHub Actions Published 7 hours ago MIT ~447 downloads/mo

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.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

pi-subagents

English | 中文

为 pi 提供极简、完整的子代理框架。

三个操作——启动、发消息、停止——配上独立上下文、并行执行、自动结果交付、可恢复会话和原生子代理窗口。生命周期完整,操作接口极简:你定义任务,框架管理代理。

作为 pi 扩展运行,除 pi 宿主提供的包外,没有额外运行时依赖。已在 pi 1.1.0 上测试。

你: 调研一下这个项目的数据库 schema
  → pi 调用 agent_spawn,spawn 一个常驻的 `pi --mode rpc` 子进程
  → 子 agent 在独立上下文窗口里独立工作
  → 结果返回;你继续聊天

为什么需要它?

Pi 没有内置 sub-agent。于是重活、并行的活、吃上下文的活,全都挤进你那一个窗口。pi-subagents 把活交给子 pi:噪音留在那边,只有答案回来。

特性

  • 安静上下文 — 日志、搜索结果、测试输出留在子窗口里。你拿到最终结果,不是一片杂乱。
  • 真正并行 — 一次起多个后台 agent,就绪结果在主代理下一个 turn 边界一起交付,每份保留独立卡片。无任务排队、无取结果工具。
  • 全程可控 — agent_send 给运行中的 agent 发消息重定向(也能唤醒空闲的),agent_stop 停止进程。最终回复自动回传,承载阻塞问题和最终汇报;默认深度下子代理另有发往 @parent 的 agent_send,只用于非阻塞问题。
  • 追问保留上下文 —— 子代理完成后默认常驻(空闲,零 token;每个约 180 MB 内存),进程已退出,或失败/停止后仍活着时,agent_send 都会从保存的会话自动恢复。子代理缺信息或需要决策时,把问题作为最后的回答结束;父代理用 agent_send 回答,子代理接着原来的上下文继续。非阻塞问题在轮中用 agent_send 发给 @parent,子代理接着做。
  • Pi 原生 — 渲染、会话、attach 全复用 pi 自己的机制;卡片看起来像内置工具——因为它们就是。
  • 继承或覆盖 — 子 agent 默认继承你的模型与推理强度,也可逐个覆盖。便宜模型做侦察,强模型做实现。
  • 无隐藏限制 — 默认无 token 上限、无超时、无并发上限。需要时用可选 timeoutMs 加一道护栏。
  • 可复盘 — 每个会话持久化、永不删除;任何结果都能 pi --session <path> 回看。
  • 按需嵌套 — 默认子代理不能再派子代理;在配置文件里调高 maxDepth 才允许。
  • Token 经济 — 系统侧保持克制:
    • 系统提示 — 工具描述只解释操作,协调规则放在按需读取的 skill 中。
    • 通知 — LLM 只看到最小结构化数据;装饰(label、用量)留在渲染层,永不进入 LLM 上下文。
    • 结果 — 自动截尾(2000 行 / 50KB);展开任何卡片仍见全量。

设计理念

Pi 是一个极简、可扩展、由你定制的代理运行框架。pi-subagents 延续这个思路:提供完整的委派机制,不替你规定工作方式。

  • 接口小,生命周期完整。 agent_spawn、agent_send、agent_stop 覆盖委派、追问和取消。结果自动送回,会话持久保存,代理可见、可查看。
  • 提供机制,不做工作流引擎。 不预设角色,不内置调度器,不强制编排。通过 prompt 定义任务,按需组合工具。协调建议放在可选 skill 中,而非运行时策略里。
  • 复用 pi,而非另造一套基础设施。 子代理是真正的 pi 进程。RPC、会话、渲染和模型配置沿用 pi 的能力,不引入另一套代理运行框架。
  • 不用就不承担额外负担。 代理工具按需启用,详细指导按需读取;UI 元数据不占模型上下文。嵌套和超时由你明确选择,不设隐藏默认限制。

极简是减少需要理解的概念,不是省掉代理生命周期。设计约定见 SPEC.md,UI 层边界见 docs/ui.md。

安装

# npm(推荐)
pi install npm:@chthub/pi-subagents

# git
pi install git:github.com/chthub/pi-subagents

# 本地目录(开发)
pi install /path/to/pi-subagents

只在一次会话里试用:pi -e /path/to/pi-subagents/index.ts。

重启 pi 后直接说"让子 agent 去…"。

快速上手

发起一个任务

让一个子 agent 分析 src/ 下的认证逻辑

交互会话中,agent_spawn 默认后台运行:子代理在独立上下文里工作,最终回复自动送回。需要工具调用等待并内联返回答案时,指定 run_in_background: false。

后台并行跑多个

同时起三个子 agent 分别看 auth 模块、数据库层和 API 路由

Pi 在同一块里发三次 agent_spawn。子代理各自独立工作,后台就绪结果在下一个交付边界自动送回;前台调用同样并行执行,答案内联返回。无需轮询,也不需要额外的取结果工具。

发消息或停止

那个数据层子 agent——方案行不通,改用组合式重写

Pi 调用 agent_send 向运行中的 agent 注入 steering(当前模型响应及工具批次结束后、下一次模型调用前送达)。要停掉失控的 agent:“干掉那个后台子 agent” → agent_stop。两者对运行中的 agent 都适用;persistent: true 的话,完成后依然可寻址——agent_send 唤醒空闲的 agent 继续追问,agent_stop 结束。

启用方式

主会话里三个 agent 工具注册时默认不激活:不派子代理的对话里不带它们的参数说明。子代理里的 agent_send 一开始就是激活的。无参数的 subagent_enable 启用工具,下一次模型请求起生效。启用记录写进会话,resume 后仍然有效。

协调 skill

包内自带 pi-subagents-workflow,是任务边界、并行、文件修改分工、后续工作和检查规则的唯一维护位置。不定义预设代理身份,也不在运行时强制工作流。

加载整个包后,新窗口默认就能看到 skill 的名称、触发描述和路径,不需要先调用 subagent_enable。模型读取 skill 或用户调用 /skill:pi-subagents-workflow 时,全文才进入上下文。包的 skill 过滤配置和 --no-skills 可以禁用发现。只用 -e 加载 index.ts 不会同时加载包内 skill。不能再派生子代理的子代理(默认深度下所有子代理都是如此)看不到这个 skill。

工具描述解释调用会发生什么,skill 说明主代理怎么组织工作。子代理即使没有读取 skill,也会收到一段解释最终回复自动回传的简短任务附言。

底部列表与子代理窗口

输入框下方列出本对话的所有子代理,一行一个(运行中 / 空闲 / 完成 / 失败 / 停止):

 ⠋ @jude  只读目录检查   12s  bash: ls -la
 ‖ @yara  keeper        idle
 ✓ @max   分析 auth 模块  1m3s
 点击进入 · /agents
  • 点击某一行(pi 默认的全屏模式下可用),或输入 /agents(/agents max 直接打开),进入该子代理的全屏窗口:用 pi 原生组件显示它的完整对话,运行中实时更新。
  • 在窗口里直接打字和它对话。 这是侧聊:回答只留在子代理自己的会话里,不会通知主 agent。已结束的子代理在你发第一条消息时用 pi --mode rpc --session <路径> 复活,之后常驻(空闲)到主会话结束或被 agent_stop。失败/停止的旧进程仍活着时,这条新消息会先关闭旧进程,再恢复同一会话;额度耗尽不再导致切换账号后仍永久无法投递。
  • 用 /continue 继续任务,并把结果回传父代理。 在子代理窗口输入 /continue,或 /continue 已切换账号,请重试。这不是私聊:必要时恢复失败/停止的子代理,并将这一轮最终回复(或失败)自动通知父代理。运行中或空闲的直接子代理也能使用。普通消息仍是侧聊;/continue 由窗口本地处理,不代表支持完整的 Pi / 命令。
  • 鼠标悬停时高亮当前行。在 tmux、zellij、screen 里 pi 不开启鼠标移动上报,所以没有悬停高亮,但点击照常可用。
  • 窗口底部是这个子代理自己的 pi 底边栏:工作目录和 git 分支、它的 token 用量、费用和上下文占用、模型和推理强度,以及它的扩展设置的状态。窗口自己的按键提示放在最后一行。
  • 连按两次 Ctrl+X 停掉正在运行或常驻的子代理(第一次只是确认提示,中间按别的键就取消)。会话文件保留,之后发消息仍可复活。如果主 agent 还在等它这一轮的结果,会收到一条 stopped 结果,说明是用户停掉的。
  • Esc 回到主对话;PgUp/PgDn 或滚轮滚动;全屏鼠标模式下可拖动右侧滚动条滑块、点击轨道跳转,拖到底部后重新跟随新输出。悬停或拖动时滑块从 ┃ 加宽为 █,不改变正文换行;悬停依赖终端上报鼠标移动。在正文上拖动可选中文字(双击选词、三击选行),松开后由 pi 复制,与主对话一致。ctrl+o 展开工具输出。
  • resume 或 /reload 后列表会从会话记录重建(进程不会保留,会话文件会)。/new、/resume、/fork 也会停掉当前会话的子代理。

配置

配置文件

<agentDir>/pi-subagents.json(默认 ~/.pi/agent/pi-subagents.json,跟随 PI_CODING_AGENT_DIR),也可以用 PI_SUBAGENT_CONFIG 指定路径。可选;启动和 /reload 时读取。

{ "maxDepth": 1 }
键 默认 含义
maxDepth 1 主会话之下允许几层子代理。1:主会话能派子代理,子代理不能再派。2:子代理还能再派一层。以主会话的值为准,整棵树生效;到达上限的子代理只保留 agent_send,且只能发给 @parent。

值不合法时退回默认值,并在 TUI 里提示。

环境变量

变量 默认值 含义
PI_SUBAGENT_SESSION_DIR <agentDir>/subagent-sessions/ 子 agent 会话存放目录;设置可迁移位置。<agentDir> 为 pi 的 agent 目录(默认 ~/.pi/agent,遵循 pi 的 PI_CODING_AGENT_DIR)。

目录遵循 pi 的 agent-dir 约定,刻意放在 pi 标准会话树之外,让 pi -r(resume)保持干净。会话文件永不删除。

工具参考

agent_spawn — spawn 一个隔离的 sub-agent

参数 类型 默认值 含义
prompt string 必填 子 agent 的自包含任务描述。
label string 必填 短标识,显示在工具卡、通知卡、widget 行和会话名上。
model string 继承 覆盖子 agent 的模型。指定但注册表中找不到 → 报错,不静默降级。
thinking "off"…"max" 继承 覆盖推理强度;省略则用当前会话的级别。
tools string[] 全部 子 agent 可见的工具白名单——白名单之外全部不可见。agent_send 总会保留,不用列出。
run_in_background boolean true 后台(默认)立即返回 agent_id,完成时投递携带最终输出的通知;false 阻塞到结果就绪。pi -p / json 模式下省略时走前台(宿主答完即退出,后台结果会丢)。子代理里没有这个参数,只能前台。
timeoutMs number 无 可选的单轮时限(毫秒),作用于任务本身和之后每一轮由 agent_send 发起的对话。触发时扩展打断这一轮、等它 settled(等不到就强制停止),并按 stopped 报告。
persistent boolean true(根会话)/ false(子代理内) 完成后常驻(空闲,零 token),追问时不用重启;agent_stop 结束它。false 时完成即退出,agent_send 仍会从会话文件复活它。

最终回复自动回传,承载阻塞问题和最终汇报。默认 maxDepth=1 时子代理唯一的 agent 工具是发往 @parent 的 agent_send,只用于非阻塞问题,协调由父代理完成。

agent_stop — 取消代理任务

参数 含义
agent_id 必填 — agent_spawn 返回的 agent id。取消正在执行的任务并停止进程;明确要求停止空闲代理时只关闭进程。优雅终止(stdin EOF);不生成新的完成通知。

停止不会删除保存的对话、撤销文件修改或取消已提交作业。空闲进程不消耗模型 token,随宿主会话退出而关闭。

agent_send — 树内发消息

参数 含义
to 必填 — agent_spawn 给到的 agent id(短人名,如 max),或 "@parent" 发给 spawn 你的会话。
message 必填 —— 消息文本;在目标当前轮结束后送达,唤醒空闲子代理,或从会话文件复活已结束的子代理(回答以通知送回)。

向空闲子代理发消息会启动新一轮模型调用;向已退出的子代理发消息会恢复保存的会话。最终回复自动返回。

工具只能寻址直接子代理,或者在子代理里寻址 @parent(它的直接父代理;到达深度上限的子代理只能发给 @parent)。父代理收到的是 {kind: "non_blocking_question", agent_id, question},完成通知则带 kind: "final_reply"。发给 @parent 的消息不结束子代理这一轮:父代理在当前回复及工具批次结束后读到,回答在子代理当前这批工具调用结束后、或这一轮结束后出现在它的对话里。父代理若正阻塞在前台 agent_spawn 里,要等这次调用返回后才读到。不能直接投递给兄弟代理,跨层协调需要父代理转发。

进阶

指定模型

用 claude-sonnet 起一个子 agent 分析数据库设计

不指定模型 → 继承当前会话模型。thinking 同理:省略时继承当前推理强度,传 "off"…"max" 可覆盖。

模型指定但注册表中找不到 → 报错,不静默降级。

限制工具

让子 agent 调研项目结构,但只允许用 read 和 grep

子 agent 看不到其他任何工具。只读探索 + 便宜模型是调研任务的推荐模式。

子进程启动集成事件

其他扩展可通过事件让子 RPC 进程加入自己的集成,不必依赖 pi-subagents,也不必另设 agent 目录。每次新建或复活 AgentProcess 前,pi-subagents 同步发出 pi.events.emit("pi-subagents:child-launch:v1", launch),载荷是全新的可变对象:

{ env: Record<string, string>, extensions: string[], error?: string }

env、extensions 初始分别为 {}、[]。监听器必须同步完成,不能用 async、Promise 或延迟修改。例如,在扩展工厂中:

const unsubscribe = pi.events.on("pi-subagents:child-launch:v1", (data) => {
  const launch = data as {
    env: Record<string, string>;
    extensions: string[];
    error?: string;
  };
  try {
    launch.env.MY_CHILD_SYNC = "1";
    launch.extensions.push("/absolute/path/to/child-bridge.ts");
  } catch {
    launch.error = "Child integration unavailable";
  }
});
pi.on("session_shutdown", () => unsubscribe());
  • 环境变量补充项覆盖继承的进程环境,但 pi-subagents 的子代理身份与深度字段优先。没有监听器时,环境继承行为不变。此机制本身不修改 PI_CODING_AGENT_DIR,也不传递凭据;请只注入非敏感协调信息,不要注入 token。
  • 扩展路径去重后作为 --extension <path> 参数传入,新会话和带 --session 的复活均适用,不替代 Pi 原有扩展发现机制。建议使用绝对路径;相对路径以子进程工作目录为基准。
  • 构造进程前会校验并复制载荷;pi-subagents 不把它写进 roster 或会话记录。环境变量、扩展字段的类型或字符串无效时拒绝启动,不做隐式类型转换。
  • 只要提供了 error 就拒绝启动,非字符串等畸形值同样拒绝。请使用不含路径或 token 的通用错误文字;pi-subagents 只报告通用拒绝原因,不回显原文。事件发出本身的异常继续向上传递。
  • Pi 会在内部捕获并记录监听器异常。 此钩子无法检测这些异常,也不等待监听器的异步工作。监听器必须自行捕获准备阶段的失败,并同步设置 error,才能确保失败时不启动子进程。
  • 订阅清理由监听器所属扩展负责;pi-subagents 不注销其他扩展的处理器。

工作原理

每个 sub-agent 都是带持久化 session 的常驻 pi --mode rpc 子进程:

  • 前台 — agent_spawn 等待子进程 settled,内联返回最终输出。非常驻子代理随后退出,常驻子代理保留以供追问。
  • 后台结果 — agent_spawn 立即返回;子代理 settled 后,结果进入会话级收件箱。主代理忙碌时,所有就绪结果在当前回复及工具批次结束后、下一次模型调用前一起送达,也就是 steer 的交付时机;通过 turn_end 实现,agent_before_settle 兜底。不打断生成或工具,不进入 follow-up 队列,不依赖全局 steering 队列的逐条/全部消费设置。空闲时只合并一个事件循环 tick 内已就绪的结果,保留独立卡片、只启动一次模型处理,不等待尚未完成的子代理。这防止排队造成的延迟,不保证报告在后续文件修改后仍然代表最新状态。
  • 发消息/停止 — agent_send 双向均用 steering(当前响应及工具批次结束后、下一次模型调用前),空闲子代理会被唤醒;agent_stop 关闭 stdin 优雅退出,不新增完成通知,但不删除已排队的有效结果。
  • 常驻空闲 — 子代理默认常驻,完成后保持 idle(零 token);之后 agent_send 可唤醒继续同一上下文,agent_stop 结束。完成通知与 widget 行带 idle 标记。
  • 恢复/失败后继续 — 通过 agent_send 或子代理窗口发送一条新消息时,进程已退出,或仍活着但状态为 failed/stopped 的直接子代理,都会从保存的会话恢复。先等旧进程退出,再启动 pi --mode rpc --session <路径>,不重启健康的运行中/空闲进程。新消息开始下一轮,不自动重发之前失败的请求。旧进程清理后重新收集启动联动信息,因此已接入的 Codex 账号切换也作用于替代进程。并发恢复共用一次启动;明确停止或主会话关闭会取消待恢复操作。主代理追问仍回传结果,用户侧聊仍保持私有;已经尝试投递但失败的消息不会被盲目重试。
  • 提问与交付 — 每个子任务末尾说明两条向上的通道怎么用。最终回复承载最终汇报和阻塞问题:被卡住时以问题结束本轮回复,等父代理用 agent_send 回答。发往 @parent 的 agent_send 只承载非阻塞问题:发出问题后接着做;不用它发阻塞问题、进度汇报或结果。完成后直接返回交付,结果自动回传,不额外发送一份完成报告。前台 spawn 的说明另加一句:父代理要等这次回复结束才读到轮中消息,所以非阻塞问题也写进回复。agent_send 默认对每个子代理开放,tools 白名单里不用列出。
  • Attach / 复盘 — sub-agent 会话存储在 <agent 目录>/subagent-sessions/(见配置)。在主会话里找到 session 路径(agent_spawn 调用结果或完成通知卡片),执行 pi --session <path>——通知里也带 path,直接问 LLM 也行。
  • Graceful turn limits(默认不限,opt-in) — 不设隐藏 deadline:子 agent 一直跑到完成或被停,除非你传 timeoutMs。不做 token 限制——用量仅通知卡统计。

嵌套 sub-agent

默认关闭(maxDepth: 1)。调高 maxDepth 之后:

子 agent 是完整 pi 实例——全局安装本扩展后,它天然能再 spawn 子 agent。每一层都是独立进程、独立上下文;深度倍增启动时间与 token 成本。是否值得,由你(或模型)判断。

机制是纯点对点投递器:agent_send 按 id(短人名,如 max)投给直接子,或投给 "@parent"——其他目标显式报错。路由由 LLM 自行完成:每个 agent 只寻址自己被给到的 id,逐跳转发(孙联系祖 = 每层父 LLM 决定用 @parent 上转)。信息披露是唯一限制——父持有它 spawn 的 agent 的 id;没被告知的 id 谁也发不到。

成本与注意

  • Headless 下子 agent 随宿主退出(pi -p)。主进程在响应结束时退出,后台子 agent 经 stdin EOF 被清理——从不留孤儿。后台流程(等通知、发消息、stop)面向常驻的 TUI 会话。
  • 一子 agent 一进程。 前台后台同是常驻 rpc 子进程。开多了 = 进程多了——请节制。
  • 结果一次性投递。 按进程及 settlement 的 completion_id 在当前运行会话内去重。同一代理不同轮次均保留,即使文本相同。主代理中断或报错后,结果只保存、不强行重启模型,供下一轮使用;若主会话先崩,结果仍在子代理 session 文件里(pi --session <path>)。
  • 常驻代理占用内存。 每个空闲代理仍是一个 pi 进程(典型扩展配置下约 180 MB RSS),直到主会话结束或被 agent_stop。一次性任务可设 persistent: false,之后仍能从会话恢复追问。
  • 恢复依赖保存的会话。 恢复的是对话上下文,不是被中断工具的内存或执行位置。

清理

pi 退出时,子 agent(运行中或 idle)都收到优雅的 stdin-EOF 关闭。session 留在磁盘供 attach/复盘;清理不会删除会话文件。

开发

开发、版本更新与发布规范统一见 AGENTS.md。

许可证

MIT。版权声明和第三方来源说明保留在 LICENSE。