pi-taskflow
v0.0.23
Published
A declarative, verifiable graph of task nodes for the Pi coding agent — not a workflow you script, but a DAG you declare: statically verified before it runs, with dynamic fan-out, gates, isolated subagent context, resumable runs, and saveable commands.
Downloads
3,939
Maintainers
Readme
pi install npm:pi-taskflowworkflow 是在「流动」,而 taskflow 是一张「图」。 其他编排框架让模型去「写脚本」——命令式的代码逐步流动,而那张图藏在控制流里。pi-taskflow 恰恰相反:你把工作声明为一张由离散、具名的任务(task)节点、通过 dependsOn 边连接而成的图——而运行时会在花掉一个 token 之前,先验证这张图。
你已经熟悉内置子代理(subagent)工具的 task / tasks / chain 了。pi-taskflow 使用完全相同的简写语法——所以你现有的委托立刻就能变成可追踪、可恢复、可保存为一条 /tf:<name> 命令的流程。当你超越简写语法时,完整的 DSL 为你提供真正的 DAG:针对数十个项目的动态并发分发、条件路由、质量门控、人工审批、重试,以及硬性费用上限。
而且自始至终,只有最终阶段(final phase)才会进入你的对话。 每一个中间转录都留在运行时中,永远不会进入你的上下文窗口。
为什么叫 “taskflow” 而不是 “workflow”?
名字就是立论。在工程语境里,**task(任务)**是一个离散、被声明出来的工作单元——是任务图的节点(构建系统、调度器、编译器都把这种 task 连成 DAG)。而 **work(工作)**息息相反,是流动的、无界的——那种连续的、命令式的「干活」过程。
这个区别,恰恰就是 Pi 生态里的设计分水岭:
- 一个
workflow(那种动态的、code-mode 的形态)是模型在写一段**「流动」的命令式脚本**:await agent(...)、一个if、一个for、又一个await。很有表达力——它是图灵完备的——但那张图只在代码跑起来的时候才存在。你看不到它、diff 不了它,也无法在付费之前证明它会终止。 - 一个
taskflow把计划从代码中移出、放进一张由task节点构成的声明式图里。 因为这张图是数据,运行时就能做到命令式脚本从结构上做不到的事:在任何子代理被启动之前就静态验证它(无环、无死端、不超预算、无悬空引用)、渲染它(实时进度本身就是那张 DAG)、逐阶段恢复它,以及把它保存为一条命令。
我们有意为之的取舍:我们放弃了任意代码的极致表达力,换来了命令式脚本永远无法拥有的东西——一张可验证、可观测、可重放、且能安全交给 LLM 生成的图。当一个任务需要十二个步骤、带分支并发分发和一道审查门控时,你要的是一张能检查的图——而不是一段你只能祈祷它跑对的脚本。
为什么需要这个
这就是你在使用原生子代理时遇到的瓶颈:你用文字描述一个多步骤计划,模型每次都要重新推导,中间转录物塞满你的上下文,一旦某次模型调用失败你就得从头开始。没有复用,没有恢复,没有结构——也没有任何办法在烧掉 token 之前检查这个计划。
pi-taskflow 把计划从提示词中移出,放入一张由任务节点构成的声明式图里。 运行时(runtime)拥有 DAG、循环、重试和中间状态的所有权。你声明一次流水线,就能按名字运行上百次。因为这个计划是数据——不是文字,也不是代码——所以它可以被验证、可视化、重放。
十二个步骤、分支并发分发、一道审查门控、一个费用上限——这就是一张图,你想要看到并检查它,而不是每次运行都重新提示一遍。
| | 子代理(内置) | pi-taskflow |
|---|---|---|
| 谁在驱动 | 模型,逐轮驱动 | 运行时,依据定义驱动 |
| 拓扑结构 | 链式 / 平面并行 | 带分层并发 + 路由的 DAG |
| 中间结果 | 在你的上下文窗口中 | 在运行时中——不在你的上下文里 |
| 规模 | 少量任务 | 动态 map 并发分发,覆盖数十个项目 |
| 可复用 | 每次重新描述 | 保存为 /tf:<name> |
| 可恢复 | ✗ | ✓ 跨会话(cross-session)——已缓存的阶段自动跳过 |
| 质量门控 | ✗ | gate 阶段,在 VERDICT: BLOCK 时停止 |
| 条件路由 | ✗ | when 守卫 + join: any 或连接(OR-join) |
| 容错 | ✗ | 逐阶段 retry + 瞬态错误自动重试 |
| 人机协作 | ✗ | approval 阶段(批准 / 拒绝 / 编辑) |
| 成本控制 | ✗ | 全运行 budget(USD / 代币上限) |
| 组合 | ✗ | flow 阶段运行已保存的子流程 |
| 实时进度 | 运行时不可见 | 实时 DAG 渲染,附带耗时和成本 |
| 易用性 | 每次内联 JSON | 简写语法(task/tasks/chain)或 DSL |
它没有取代子代理工具。它给你的子代理赋予了一张图、一份记忆和一个名字。
声明式图 vs 命令式脚本
精神上最接近 pi-taskflow 的,是那种动态 / code-mode 的 workflow——模型写一段 JavaScript 编排脚本。它强大、且确实很有表达力。但它位于某个根本轴的另一极:表达力 vs 可验证性。
| | 动态 workflow(code-mode) | pi-taskflow(声明式图) |
|---|---|---|
| 计划是什么 | 模型书写并运行的命令式 JS | 运行时执行的声明式 JSON 数据 |
| 那张图 | 隐式——藏在 if/for/await 控制流里 | 显式——phases[] + dependsOn 边,一等对象 |
| 运行前验证 | ✗ 图灵完备;无法证明会终止 | ✓ 静态检查:无环、无死端、不超预算、无悬空引用 |
| 看到它 | ✗ 图只在代码跑起来时存在 | ✓ 实时进度渲染本身就是 DAG |
| 恢复 | 粗粒度(调用缓存去重) | ✓ 逐阶段输入哈希恢复,跨会话 |
| 能否安全交给 LLM 生成 | 有风险——它是可执行代码 | ✓ 它只是数据——无 eval、无任意执行 |
| 表达力上限 | 更高——任意控制流 | 受 DSL 限制(但 map/when/loop/gate 覆盖了大多数任务) |
我们有意选了可验证的那一边。你放弃的表达力是真实的;但你换回的——一张能检查、能看、能重放、能安全交给模型书写的计划——才是把一次性提示变成持久编排的关键。
与其他 Pi 扩展的对比
Pi 生态现在有 20 多个委托、工作流和编排扩展——每个在各自领域都很出色。以下是一份诚实的定位图(已对照每个包截至 2026 年 6 月的最新 npm 发布版核实)。完整的对比——每个包的优缺点——请参见 PI-ECOSYSTEM.md。更广泛的非 Pi 生态对比(LangGraph、Temporal、CrewAI、Mastra……)请参见 COMPETITORS.md。
| 扩展 | 模型 | 自定义 DSL | DAG | 动态并发分发 | 跨会话恢复 | 质量门控 | 人工审批 | 保存为命令 | 零依赖 |
|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| pi-taskflow | 声明式多阶段 taskflow | ✓ | ✓ | ✓ map | ✓ phase-hash | ✓ | ✓ | ✓ /tf:<name> | ✓ |
| @pi-agents/orchid | 固定 9 阶段流水线 + Ralph 循环 | 固定 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✕ (2) |
| pi-crew | 角色团队 + git worktree + 异步 | 部分 | ✓ | ✓ | ✓ | ✓ | ✓ | – | ✕ (7) |
| ultimate-pi | 受管制的 plan→execute→review 框架 | YAML 合约 | ✓(规划时) | ✕ | ✓ | ✓(3 级) | ✓ | ✓ | ✕ (16) |
| @zhushanwen/pi-workflow | JS 脚本(agent/parallel/pipeline) | 是(JS) | ✕(线性) | ✓ | ✓ | ✕ | ✕ | ✓(调用缓存) | ✓ |
| @fiale-plus/pi-rogue-orchestration | 定时器循环 + 目标解析 | ✕ | ✕ | ✕ | ✓ | ✓(目标检查) | ✕ | ✕ | ✓ |
| pi-subagents | 单/并行/链式委托 | ✕ | ✕ | 静态 | – | ✕ | clarify | 命名工作流 | ✕ (3) |
| @gotgenes/pi-subagents | Claude-Code 风格子代理 + worktree | ✕ | ✕ | ✕ | ✓(按 id) | ✕ | 逐代理 | ✕ | ✕ (1) |
| pi-pipeline | 固定 SPEC→PLAN→TASKS→VERIFY | ✕ | 固定 | ✕ | 会话内规划 | ✓ | clarify | ✕ | ✕ (2) |
| pi-agent-flow | 一次性并行专业 fork | 是 | ✕ | ✕ | – | ✕ | ✕ | – | ✕ (2) |
(20 多个扩展的代表性切片——完整列表及 @0xkobold/pi-orchestration、@melihmucuk/pi-crew、@mediadatafusion/pi-workflow-suite、gentle-pi、@dreki-gg/pi-subagent 等请参见 PI-ECOSYSTEM.md。)
如何选择:
@pi-agents/orchid是生态中功能最完整的编排器(DAG + worktree + Ralph 循环 + 代理邮箱)——但其 DSL 是固定的 9 阶段流水线,携带运行时依赖 + jiti,且处于 beta 阶段。当你想定义自己的图结构(而非采用别人的固定观点),并且追求零依赖和一条命令安装时,选pi-taskflow。pi-crew/ultimate-pi更重——worktree 隔离、持久的异步团队、多层治理。如果你想要轻量、声明式、零依赖,那就选本项目。@zhushanwen/pi-workflow精神上最为接近,也是零依赖,但它站在上述分水岭的命令式那一边:你要以模型书写并运行的 JavaScript 脚本来编写工作流。pi-taskflow的声明式 JSON DAG 是可验证的那一边——可静态检查、可可视化、可安全交给 LLM 生成,且恢复粒度精细到阶段级别而非调用缓存去重。@fiale-plus/pi-rogue-orchestration拥有真正的循环至完成(pi-taskflow尚不具备的功能)。如果你的任务是"一直做直到目标达成",它值得一看;而pi-taskflow适用于结构化、分支式的流水线。pi-subagents/@gotgenes/pi-subagents是即席"用 reviewer 审查这个 diff"委托和后台作业的成熟选择。pi-taskflow则适用于当这些委托需要变成可重复、可恢复的流水线时。pi-pipeline/pi-agent-flow提供的是固定观点、固定结构的流程。pi-taskflow提供的是一张空白画布:你(或模型)声明适合任务的图结构。
诚实的一句话总结:
pi-taskflow是唯一一个给你一张声明式、可验证、可恢复的任务节点 DAG 的 Pi 扩展——保存为一条单词命令,零运行时依赖,且从设计上就上下文隔离。 code-mode 的 workflow 让模型去写脚本跳动工作,pi-taskflow则让它声明一张运行时能在执行前证明其正确的图。 已知正在弥补的缺口:循环至完成、worktree 隔离、非阻塞后台运行(详见STRATEGY.md)。
30 秒快速开始
1. 安装——一条命令:
pi install npm:pi-taskflow可选: 运行
/tf init一次,将 18 个内置代理的模型角色(model role) (fast、strong、thinker……)映射到你已启用的模型——交互式选择器。 跳过此步骤则代理使用 Pi 的默认模型。参见模型角色。
2. 运行——直接在 Pi 会话中告诉模型:
运行一个链式任务:先探索认证流程,然后总结发现。
模型会自动调用 taskflow 工具。你会看到实时进度、每步耗时、代币成本和已保存的运行记录——与内置工具同样省力,现在可追踪且可恢复。
3. 保存——说一句 "save it",你就永远拥有了 /tf:<name>。
就这么简单。你可以在咖啡凉下来之前运行第一个工作流——无需编写任何阶段定义。
简写语法(与内置工具相同的格式)
// 单一任务——一个代理,一个任务
{ "task": "Summarize the architecture of src/", "agent": "explorer" }
// 并行——同时触发多个任务,输出合并
{ "tasks": [
{ "task": "Audit auth in src/api", "agent": "analyst" },
{ "task": "Audit input validation in src/api", "agent": "analyst" }
] }
// 链式——顺序执行;每个步骤可以看到前一个步骤的输出
{ "chain": [
{ "task": "List the public API of src/lib", "agent": "scout" },
{ "task": "Write docs for:\n{previous.output}", "agent": "writer" }
] }agent 是可选的(默认使用第一个发现的代理)。添加 name 来标记运行并解锁将其保存为命令的功能。
看看它如何运行
这不是模拟图。这是真实运行的 stdout——self-improve 流程会编写并验证自己的测试套件,被质量门控在中途捕获:
⊗ taskflow self-improve 6/7 · blocked · $0.095
✓ discover agent deepseek-v4-flash 10t ↑38k ↓6.7k $0.011
┌ ✓ write-runner-tests agent claude-sonnet-4-6 10t ↑13 ↓6.6k $0.020
├ ✓ write-store-tests agent claude-sonnet-4-6 10t ↑11 ↓10k $0.018
├ ✓ write-agents-tests agent claude-sonnet-4-6 10t ↑28 ↓13k $0.030
└ ✓ fix-stability agent claude-sonnet-4-6 10t ↑13 ↓3.9k $0.012
✓ verify gate BLOCK 3 type errors in test files deepseek-v4-flash
⊘ report reduce skipped · Gate blocked ↳ fix-stability布局本身就是 DAG。 没有仪表盘,没有需要 grep 的日志——你看一眼进度条就了解了整个流水线:
- 头部(header)——
⊗= 被阻塞(门控将其停止);6/7个阶段已处理;累计成本$0.095。 - 状态图标——
✓完成 ·◐运行中 ·✗失败 ·⊘已跳过 ·○等待中。 - 轨道线
┌ ├ └——同一 DAG 层中的阶段,并发运行。四个write-*/fix-stability任务从discover并发分发出去。空白侧边线 = 单阶段层。 ↳——跨层长距离依赖。report依赖于相邻的verify以及两层级之前的fix-stability,因此只标注了这条跳过边。- 门控(gate)——
verify发出了VERDICT: BLOCK,因此运行时跳过了report,以blocked状态结束运行,并将原因内联展示。 - 详情——每阶段:模型、代币数量(
↑输入↓输出)、成本、耗时。并发分发阶段还显示子任务进度(3/15 2✗ 8▸)。
走向声明式
简写语法是你的入口。DSL 才是 pi-taskflow 的真正价值所在——动态并发分发、结构化路由和质量门控。
并发分发与归约
{
"name": "summarize-files",
"description": "Discover files, summarize each, produce one report",
"args": { "dir": { "default": "." } },
"concurrency": 8,
"phases": [
{ "id": "discover", "type": "agent", "agent": "scout",
"task": "List source files under {args.dir} (non-recursive).\nOutput ONLY a JSON array [{\"file\":\"\"}]. No prose.",
"output": "json" },
{ "id": "summarize", "type": "map",
"over": "{steps.discover.json}", "as": "item", "agent": "scout",
"task": "Read {item.file} and give a one-sentence summary.",
"dependsOn": ["discover"] },
{ "id": "report", "type": "reduce", "from": ["summarize"], "agent": "writer",
"task": "Combine into a short overview:\n{steps.summarize.output}",
"dependsOn": ["summarize"], "final": true }
]
}discover列出每个文件并输出一个 JSON 数组。summarize是一个map——它为每个文件并发分发一个子代理,最多 8 个并发,{item.file}绑定到每个文件路径。report是一个reduce——它将所有摘要合并为一个干净的概述。
中间的摘要永远不会进入你的上下文。运行时拥有它们;你获得报告。保存一次 → 永久可用 /tf:summarize-files dir=src。
路由、门控、重试、审批与费用上限
{
"name": "triage-and-fix",
"budget": { "maxUSD": 1.5 },
"phases": [
{ "id": "triage", "type": "agent", "agent": "analyst", "output": "json",
"task": "Classify the bug. Output ONLY {\"severity\":\"high\"} or {\"severity\":\"low\"}." },
{ "id": "deep", "when": "{steps.triage.json.severity} == high", "dependsOn": ["triage"],
"agent": "executor-code", "task": "Root-cause and patch it.",
"retry": { "max": 2, "backoffMs": 500 } },
{ "id": "quick", "when": "{steps.triage.json.severity} == low", "dependsOn": ["triage"],
"agent": "executor-fast", "task": "Apply the quick fix." },
{ "id": "approve", "type": "approval", "join": "any", "dependsOn": ["deep", "quick"],
"task": "Review the fix before it ships." },
{ "id": "ship", "type": "agent", "dependsOn": ["approve"],
"task": "Open a PR with the change.", "final": true }
]
}when根据分诊 JSON 的结果路由到deep或quick——另一个分支被跳过。join: "any"让approve在任意一个分支运行完成时立即触发(或连接 OR-join)。retry以回退策略重试不稳定的补丁;budget在成本过高时停止整个运行。approval暂停等待人工操作(批准 / 拒绝 / 编辑),然后才进入最终的ship。
无需脚本。无需 eval。运行时执行的是纯粹的数据——可以安全地直接运行 LLM 生成的定义。
阶段类型
| 类型 | 功能 | 必填字段 |
|------|--------------|-----------------|
| agent | 一个子代理运行单个任务 | task |
| parallel | 并发运行 branches[] | branches({task, agent?} 数组) |
| map | 并发分发到一个数组——每个项目一个子代理,{item} 绑定 | over、task |
| gate | 质量/审查步骤,可以暂停流程 | task |
| reduce | 将 from[] 阶段的输出聚合为一个 | from、task |
| approval | 人机协作暂停——批准 / 拒绝 / 编辑 | — |
| flow | 将一个已保存的子流程作为阶段运行(组合) | use |
| loop | 迭代一个任务直到完成——重复运行主体直到条件满足、收敛或达到上限 | task、until |
| tournament | N 个变体竞争,评判者选择最佳(或聚合) | task | branches |
通用阶段字段
每个阶段需要唯一的 id 和 type(默认为 agent)。除各类型特有字段外:
| 字段 | 含义 |
|---|---|
| agent | 要运行的代理(默认为第一个发现的代理) |
| dependsOn | 本阶段等待的阶段 id——构建 DAG |
| join | "all"(默认)等待所有依赖;"any" 为或连接 |
| when | 条件守卫——表达式为真时才执行,否则跳过 |
| retry | { max, backoffMs?, factor? }——重试失败的子代理 |
| output | "text"(默认)或 "json"(暴露 {steps.ID.json}) |
| model / thinking / tools | 子代理的逐阶段覆盖设置 |
| cwd | 子代理的工作目录 |
| concurrency | map / parallel 的并发分发上限(覆盖流程默认值) |
| final | 标记为结果承载阶段(否则最后一个阶段胜出) |
| optional | 此处失败不会中止运行 |
| use / with | (flow)已保存的子流程名称及其参数 |
| cache | { scope, ttl?, fingerprint? }——跨运行记忆化(见下文) |
流程级键:name、description、args、concurrency(默认 8)、agentScope 和 budget: { maxUSD?, maxTokens? }。
控制流与可靠性
when——除非表达式为真,否则跳过阶段。支持{refs}、== != < > <= >=、&& || !、括号以及带引号的字符串/数字。配合合并阶段的join: "any"实现真正的 if/else 路由。解析错误开放失败(fail open)。join: "any"——或连接:阶段在一个依赖完成后立即运行(默认"all"等待所有依赖)。retry——{ "max": 2, "backoffMs": 500, "factor": 2 }以固定或指数回退策略重试失败的子代理;使用量累加,尝试次数以↻N形式在 TUI 中显示。瞬态提供商错误(速率限制 / 5xx / 超时)即使没有显式策略也会自动重试;硬错误不会。approval——暂停等待人工操作(批准 / 拒绝 / 编辑)。拒绝会中止流程;编辑会将输入内容作为阶段输出注入下游步骤。非交互式运行自动批准。flow——{ "type": "flow", "use": "deep-research", "with": { "topic": "{item}" } }将保存的流程作为阶段运行(循环递归会被检测并拒绝)。
循环至完成(loop)
有些工作天生就是迭代式的——修改草稿直到评审满意、重试并改进直到测试通过、收敛到最终答案。一个 loop 阶段会反复运行一个任务体,直到停止条件成立:
{
"id": "refine",
"type": "loop",
"task": "Improve this draft (iteration {loop.iteration}). Previous attempt:\n{loop.lastOutput}\n\nReturn JSON {\"draft\":\"…\",\"done\":true|false}.",
"until": "{steps.refine.json.done} == true", // 迭代自身的输出在这里暴露
"output": "json",
"maxIterations": 6, // 默认 10,硬上限 100——循环一定会终止
"convergence": true // 默认:如果某次迭代的输出与前一次完全一致则提前停止
}- 主体局部变量——任务可以读取
{loop.iteration}(从 1 开始)、{loop.lastOutput}(前一次迭代的输出)和{loop.maxIterations},以基于自身之前的输出继续构建;这三个变量对until条件同样可用。 until——每次迭代后评估,迭代输出以{steps.<thisId>.output}/.json暴露。运算符与when相同。一旦表达式为真,循环立即停止。- 总是会终止。 四种独立停止方式:
until为真、收敛(不动点——输出与前一次迭代完全一致)、maxIterations(硬上限 100)、或迭代失败(阶段失败,部分输出保留)。格式错误的until会停止循环而非永远旋转(故障安全),并在阶段上显示警告。 - TUI 中显示
↻N及停止原因(done/converged/max/failed);使用量跨迭代累加。与gate/approval一样,loop被排除在cross-run缓存之外(每次运行必须从头迭代)。
锦标赛(tournament)
对于开放式工作,最佳结果往往来自生成多个候选并由评判者挑选最强的一个——best-of-N 带评判者,一个声明式阶段搞定:
{
"id": "headline",
"type": "tournament",
"task": "Write a punchy headline for this launch post.",
"variants": 4, // 生成 4 个相同任务的竞争者(默认 3,最多 20)
"judge": "Pick the headline with the strongest hook and clearest promise.",
"judgeAgent": "reviewer", // 可选;默认使用阶段指定的 agent
"mode": "best" // "best"(默认)| "aggregate"
}- 竞争者——要么
variants: N生成同一task的 N 份拷贝(多样性来自模型的不确定性),要么使用不同的branches: [{task, agent?}, …]当你想让不同方法相互竞争时。 - 评判者——并发分发完成后,一个评判代理看到所有变体(编号排列)及你的
judge评分标准,通过WINNER: <n>行或{"winner": n}选择胜者。无法解读的判定开放失败为变体 1;评判失败也会回退——工作成果不会丢失。 mode——best逐字返回胜出的变体;aggregate返回评判者综合各部分精华的答案。- **短路:**如果只有一个竞争者存活,它直接获胜无需评判调用;如果全部失败,阶段失败。TUI 显示
⚑ N→#k;使用量累加变体 + 评判者。与gate一样,被排除在cross-run缓存之外。 budget——整个运行的{maxUSD, maxTokens}上限;一旦超过,等待中的阶段跳过,正在运行的并发分发停止派发新任务,运行以blocked状态结束。- 空闲看门狗(idle watchdog)——子代理静默 5 分钟会被视为卡死并被终止(SIGTERM → SIGKILL),因此一个挂起的子进程永远无法冻结整个流程。
跨运行记忆化(cache)
每个阶段本身已经是内容寻址的:在单次运行的恢复中,已解析输入未变的阶段会被跳过。cache 将这个复用扩展到独立的运行之间——如果任何之前的运行计算过相同输入哈希的阶段,其结果被复用,花费**$0.00**。
{
"id": "analyze-auth",
"task": "Summarize how the auth module works.",
"context": ["src/auth/**/*.ts"],
"cache": {
"scope": "cross-run", // "run-only"(默认)| "cross-run" | "off"
"ttl": "6h", // 可选最长寿命,命中超过此时间视为未命中
"fingerprint": ["git:HEAD", "glob:src/auth/**/*.ts"] // 将世界状态折叠到键中
}
}scope——"run-only"(默认)即历史行为(仅运行内恢复)。"cross-run"将阶段选择加入持久化存储。"off"完全禁用复用(甚至运行内),用于调试。- 新鲜度是关键。 缓存键已包含提示词、
over项目和任何context文件(预读到任务中)。fingerprint将隐式输入折叠到键中,使得"世界变了"成为缓存未命中:git:HEAD、glob:<pat>(大小+修改时间)、glob!:<pat>(内容哈希)、file:<path>、env:<NAME>。ttl(30m/6h/7d)是时间安全网。 - 诚实的限制:一个子代理读取了未在
context/fingerprint中声明的文件,仍可能返回过时的cross-run命中。这就是为什么默认值是run-only,以及为什么gate/approval阶段禁止使用cross-run(它们必须在每次运行中产生新鲜的结果)。只对输出是声明输入函数的那类阶段选择加入。 - 缓存位于
.pi/taskflows/cache/(被 gitignore 忽略)。使用action: "cache-clear"清除。完整理由参见docs/rfc-cross-run-memoization.md。
门控阶段(质量控制)
一个 gate 阶段运行一个代理来审查上游输出,并可以阻止工作流的其余部分。 通过以下方式结束门控任务,让运行时能够读取判决:
- 末尾行
VERDICT: PASS或VERDICT: BLOCK(也接受OK、FAIL、STOP、REJECT、HALT——按最后一次出现为准),或 - JSON 格式如
{"continue": false, "reason": "missing auth checks"}/{"verdict": "block", "reason": "..."}。
BLOCK 时,下游阶段被跳过,运行以 blocked 状态结束,原因内联展示。模糊的输出开放失败(视为 PASS)——门控永远不会意外中止你的流程。
Review the audit below. If any endpoint is missing auth, end with
"VERDICT: BLOCK" and a one-line reason; otherwise end with "VERDICT: PASS".
{steps.audit.output}插值与表达式
| 占位符 | 解析为 |
|---|---|
| {args.X} | 调用参数 |
| {steps.ID.output} | 之前阶段的文本输出 |
| {steps.ID.json} | 之前输出解析为 JSON(或 {steps.ID.json.field}) |
| {item} / {item.field} | map 阶段中的当前项目 |
| {previous.output} | 紧邻的上游阶段的输出 |
条件语法(用于 when):== != < > <= >=、&& || !、括号、带引号的字符串/数字,以及任何 {...} 引用——例如:"when": "{steps.triage.json.route} == deep && {args.force} != true"。
引用未在
dependsOn中声明的{steps.X}是硬性验证错误——运行时在第一个代理运行之前就能捕获这个最常见的流水线 bug。
命令
保存的流程变成 CLI 快捷方式。所有命令在 Pi 会话中运行:
| 命令 | 功能 |
|---|---|
| /tf list | 列出所有已保存的流程 |
| /tf run <name> [args] | 运行已保存的流程(例如 /tf run summarize-files dir=src) |
| /tf show <name> | 打印流程的定义 |
| /tf runs | 浏览近期运行历史(交互式 TUI) |
| /tf resume <runId> | 继续一个暂停/失败的运行——已缓存的阶段自动跳过 |
| /tf init | 交互式映射模型角色到你的已启用模型(写入 ~/.pi/agent/settings.json) |
| /tf:<name> [args] | 快捷方式——一键运行流程 |
工具动作(由模型使用):run(内联 define 或已保存的 name)、save、resume、list、init。
跨会话恢复
taskflow 运行与你的会话无关。每个已完成的阶段都写入磁盘,因此失败(或你中止)的运行可以通过 /tf resume <runId> 后续继续——已缓存的阶段自动跳过,只有剩余的工作会消耗代币。
恢复以每个阶段的输入哈希为键——如果上游输出发生了变化,依赖的阶段会重新运行;如果没有变化,则复用结果。没有其他 Pi 扩展能做到跨会话的这一点。
存储
.pi/taskflows/<name>.json # 项目级定义(提交以共享)
~/.pi/agent/taskflows/<name>.json # 用户级定义
.pi/taskflows/runs/<runId>.json # 运行状态以供恢复(gitignore 此项)提交
.pi/taskflows/,你的整个团队共享流水线——无需配置同步,无需新手指南。运行状态通过原子写入写入,并由零依赖的文件锁保护,因此并发运行永远不会损坏索引。
代理发现范围(通过流程定义中的 agentScope):
| 值 | 发现代理的来源 |
|---|---|
| "user"(默认) | ~/.pi/agent/agents/*.md |
| "project" | .pi/agents/*.md(向上遍历目录树) |
| "both" | 用户 + 项目;名称冲突时项目覆盖 |
代理
Taskflow 自带 18 个内置代理——每个代理是一个 .md 文件,包含调优的系统提示词、推理级别和工具集。安装后你可以在任何阶段或简写语法中通过 name 引用它们。无需任何设置。
内置代理列表
| 代理 | 角色 | 推理级别 | 默认角色 |
|---|---|---|---:|---|
| executor | 执行规划的代码变更 | high | {{fast}} |
| executor-fast | 简单修复(≤2 文件,≤50 行) | off | {{fast}} |
| executor-code | 复杂多文件实现 | high | {{strong}} |
| executor-ui | 前端 / 样式 / 视觉变更 | high | {{vision}} |
| scout | 快速代码库侦查与文件映射 | off | {{fast}} |
| planner | 实现计划创建 | high | {{strong}} |
| analyst | 需求分析,歧义检测 | high | {{thinker}} |
| critic | 推理过程中的内联自我质疑 | xhigh | {{thinker}} |
| reviewer | 通用代码 / 架构审查 | high | {{strong}} |
| risk-reviewer | 后端 / 基础设施 / 数据库 / API 风险 | high | {{reasoner}} |
| security-reviewer | 安全漏洞,认证/加密 | xhigh | {{reasoner}} |
| plan-arbiter | 计划质量门控(复杂任务) | high | {{arbiter}} |
| final-arbiter | 评判者意见冲突时的裁决者 | xhigh | {{arbiter}} |
| test-engineer | 设计并实现测试 | high | {{fast}} |
| doc-writer | 文档撰写 | off | {{fast}} |
| recover | 压缩后的会话恢复 | low | {{fast}} |
| verifier | 运行测试,验证结果 | off | {{fast}} |
| visual-explorer | Figma 设计元数据分析 | high | {{vision}} |
代理是分层的:内置 → 用户(~/.pi/agent/agents/)→ 项目(.pi/agents/)。同名用户或项目代理覆盖内置代理——因此你可以自定义任何代理而无需修改包本身。
模型角色
每个内置代理的 model 字段使用角色占位符(例如 {{fast}})而不是硬编码的提供商字符串。这样将意图与实现解耦——你只需将角色映射到模型一次,所有代理就会自适应。
| 角色 | 意图 | 典型模型 |
|---|---|---|
| {{fast}} | 便宜快捷——高容量、低风险 | DeepSeek V4 Flash |
| {{strong}} | 平衡——规划、审查、中等复杂度 | MiMo v2.5 Pro |
| {{thinker}} | 深度分析——需求、批判 | DeepSeek V4 Pro |
| {{arbiter}} | 最终判断——裁决、计划质量门控 | Qwen 3.7 Max |
| {{vision}} | 多模态——UI 工作、设计解读 | MiniMax M3 |
| {{reasoner}} | 谨慎推理——安全、风险 | GLM 5.1 |
不配置时,代理回退使用 Pi 的默认模型。要将角色映射到真实模型,运行交互式设置:
/tf init/tf init 从一个行动菜单开始。首次用户会看到一个 2 选项快捷方式("使用推荐默认值" / "配置每个角色")。回访用户会看到完整的 5 选项菜单:
? What do you want to do with model roles?
❯ Use recommended defaults
Configure each role
Edit one role
Show current roles
Cancel选择器显示模型显示名称,附带能力标记和当前/推荐标记:
? Model for 'vision' — Multimodal (executor-ui, visual-explorer)
Current: openrouter/anthropic/claude-sonnet-4-6
Recommended: minimax/MiniMax-M3
───────────────
❯ MiniMax M3 (minimax/MiniMax-M3) · image ✓ · reasoning ✓ · (recommended)
Claude Sonnet 4.6 (openrouter/anthropic/...) · image ✓ · reasoning ✓ · (current)
GPT-5 (openrouter/openai/gpt-5) · image ✓
DeepSeek V4 Flash (openrouter/deepseek/v4-flash)
───────────────
Custom (type your own)
Keep current
Back to action menu保存之前,一个预览屏幕会显示你的变更差异:
? Review changes:
fast openrouter/deepseek/deepseek-v4-flash (unchanged)
strong openrouter/xiaomi/mimo-v2.5-pro (unchanged)
thinker openrouter/qwen/qwen3.7-max (changed ← was: openrouter/deepseek/v4-pro)
arbiter openrouter/qwen/qwen3.7-max (unchanged)
vision minimax/MiniMax-M3 (unchanged)
reasoner z-ai/glm-5.1 (unchanged)
───────────────
❯ Save these changes
Edit a role
Cancel你的选择会被写入 ~/.pi/agent/settings.json:
{
"modelRoles": {
"fast": "openrouter/deepseek/deepseek-v4-flash",
"strong": "openrouter/xiaomi/mimo-v2.5-pro",
"thinker": "openrouter/deepseek/deepseek-v4-pro",
"arbiter": "openrouter/qwen/qwen3.7-max",
"vision": "minimax/MiniMax-M3",
"reasoner": "z-ai/glm-5.1"
}
}随时手动编辑这些值,或重新运行 /tf init。
若需自定义特定代理的模型或 thinking 而不修改 modelRoles,可在 ~/.pi/agent/agents/<name>.md 创建代理文件,在 YAML frontmatter 中覆盖。
工具路径(action="init")
模型也可以通过 taskflow 工具配置角色:
| 模式 | 行为 |
|---|---|
| mode: "show"(默认) | 只读报告当前 modelRoles。从不覆盖。 |
| mode: "apply-defaults" + force: true | 将 RECOMMENDED_DEFAULTS 写入 settings.json,保留旧键。 |
| mode: "interactive" | 启动完整的行动菜单 + 选择器流程(需要 UI 会话)。 |
自定义代理
将 .md 文件放入 ~/.pi/agent/agents/(用户级)或 .pi/agents/(项目级,可提交)来添加你自己的代理:
---
name: my-linter
description: Run ESLint and report violations
tools: read, bash
model: "{{fast}}"
thinking: off
---
You are a linting agent. Run `npx eslint --format json` on the
provided files. Report violations grouped by file. No fixes.然后在任何阶段中引用它:{ "agent": "my-linter", "task": "Lint src/" }。
示例
examples/ 中已准备好可直接阅读的定义:
| 文件 | 演示内容 |
|---|---|
| summarize-files.json | discover → map 并发分发 → reduce |
| conditional-research.json | when 路由 + join: any + gate + budget |
| guarded-refactor.json | approval(人机协作)+ retry + gate |
将其中一份复制到 .pi/taskflows/<name>.json(或 ~/.pi/agent/taskflows/),它就会注册为 /tf:<name>——或者直接让模型指向它。
内部构成
0 个运行时依赖 · 535 个测试 · 9 种阶段类型 · 跨会话恢复 · 跨运行记忆化 · ~5.4k LOC 运行时
- 零运行时依赖。 没有
dependencies字段——运行时完全基于 Node 内置模块(fs/path/os/child_process/crypto)。文件锁是fs.openSync("wx"),不是第三方库。 - 535 个测试分布在 21 个测试文件中,涵盖并发、原子文件锁定(8 进程竞争回归测试)、路径穿越防御、跨会话恢复、跨运行缓存新鲜度(流程/推理/工具键隔离、指纹失效、TTL/LRU 淘汰)、门控判决、预算上限、重试/回退、审批流程、循环终止、锦标赛评判、子流程组合、回调隔离、空闲看门狗、模型角色 init 配置,以及带括号模型名称回归的 parseModelFromLabel。
- 经过强化的设计。 路径穿越防御(词法 +
realpath)、runId 验证、HTML/错误净化、原子写入、通过rename实现的过期锁窃取,以及杀死卡死子代理的空闲看门狗。 - 自产自用(dogfooded)。 每个新功能必须在发布前通过项目自身的
self-improvetaskflow 的考验。
🍽️ 我们吃自己的狗粮
pi-taskflow 中的每个功能都是通过 pi-taskflow 自身发布的。
我们的 self-improve 流程是一个 10 阶段 DAG——它审计代码库、修补缺陷、验证正确性、进行质量门控并展示报告——全部以声明式完成。它被保存为 /tf:self-improve,并在每次发布之前运行。Pi 生态中没有其他代理编排器用自身来构建自己。
| 活动 | 规模 | 阶段数 | 结果 |
|----------|-------|--------|---------|
| v0.0.8 dogfood | 全代码库审计 → 分类 → 修复 → 验证 | 10 阶段,234 个测试 | 13 个修复,全部通过 |
| v0.0.6 自审计 | 盘点 → 映射审计 → 门控 → 审批 → 映射修复 → 归约 | 9 阶段 | 修复 11 个关键缺陷 |
| 跨运行缓存 dogfood | 真实运行时 + 磁盘存储 | 专用测试框架 | 在对抗性指纹下验证缓存正确性 |
| 对抗性交叉审查 | 多代理对抗性审查 | tournament + gate | 修复 P0 缓存键问题并发布 |
| Init 重设计审查 | 必要性审计 → 并行检查 → 判决 | 7 阶段 | 完整重设计方案已验证 |
| 第 2 轮对抗性审计 | 逐阶段 DAG 执行——12 个发现覆盖 runner/runtime/interpolate/verify | 14 阶段 | 已修复 10 项,0 退化 |
| 第 3 轮对抗性审计 | 集成层 + 跨模块——10 个发现覆盖 index/agents/cache/render/runs-view | 9 阶段 | 已修复 10 项,0 退化 |
元点评: 我们使用了
pi-taskflow的map并发分发、gate判决、approval人机协作、tournamentbest-of-N、loop循环至完成和cross-run缓存——来构建pi-taskflow。
状态与边界
v0.0.17——循环至完成(loop 阶段:迭代至条件满足、收敛或上限)、锦标赛(best-of-N 带评判者)、跨运行记忆化(基于 git/文件/glob/环境指纹和 TTL 的内容寻址缓存)、交互式 /tf init(带角色感知模型选择器 + 差异预览 + 原子合并写入)、18 个内置代理及 6 个模型角色。完整的控制流与可靠性层(when 守卫、join: any、retry/回退、approval、flow 组合、budget 上限、空闲看门狗)构建在 DSL + DAG 运行时(agent/parallel/map/gate/reduce)之上。支持内联 + 已保存流程、跨会话恢复、实时进度和上下文隔离。一次运行作为一个流式工具调用执行。
已知边界(已追踪、有限定——不会在流程中途出现意外):
- 无后台执行。 运行需要 Pi 会话保持打开。真正的后台执行(以及基于它的事件/定时触发)已在路线图中。
- 无
output: "file"。 输出只能是文本/JSON——通过代理的write工具调用写入文件。 map需要一个 JSON 数组。over字段必须解析为{steps.ID.json}数组。先用一个单代理output: "json"阶段包装文本列表。- DAG 必须是无环的。 循环会在验证时被拒绝。
开发
npm install
npm run typecheck
npm test # 单元测试——无网络,无进程派生
npm run test:e2e # 真实端到端测试(派生真实子代理;需要模型访问权限)运行时位于 extensions/,测试位于 test/,可运行示例位于 examples/。
贡献
欢迎贡献——这是一个年轻、快速发展的项目。在 GitHub 上提交 issue 或 PR。适合初学者的贡献内容包括:新的示例流程、阶段类型创意和 TUI 打磨。
许可
MIT
