Skip to content

工作流与编排

·
  • pi-subagents
  • v0.67.0
  • Pi
本页为 pi-subagents 官方文档的非官方中文翻译,请以官方英文文档为准。 原文 · 许可证

如何组合子智能体:推荐的模式、打包的提示词快捷方式、脚本化工作流、直接命令、工作树隔离,以及子到父的协调。

推荐的编排模式 ​

将编排用作父智能体的指引,而非运行时工作流模式。对于实现类工作,推荐的循环是:

text
clarify → scout → worker → fresh reviewers → worker

当启动省略 context 时,打包的 worker、oracle 与 advisor 默认为 fork 上下文。如果父会话没有持久化的会话文件或当前叶子节点,该隐式默认会回退到 fresh。当你有意希望子运行为全新上下文时,传递 context: "fresh";当 fork 必须保持严格时,传递 context: "fork"。

子安全边界在运行时强制执行:

  • 子会话不会接收到打包的 pi-subagents 技能。
  • Fork 的子上下文过滤会移除仅限父级的子智能体产物(包括旧的隐藏编排指令消息、斜杠/状态/控制消息,以及先前的父 subagent 工具调用/工具结果历史),同时保留普通的散文与不相关的工具调用/结果。
  • 默认情况下,子智能体不会注册 subagent 工具,并收到以下边界指令:它们不是父编排者,不得提议或运行子智能体。
  • 显式例外是已解析的内置 tools 包含 subagent 的智能体;该子智能体获得一个子安全的 subagent 工具,用于父会话分配的扇出工作,仍受 maxSubagentDepth 约束。

失败泳道的恢复与执行模式边界 ​

子智能体工作流、子启动、提示词运行时、扩展加载或子工具设置中的失败属于泳道基础设施阻塞器。它不是通过 interactive_shell、pi -ne、Codex/Claude/Cursor CLI、前台智能体或其他外部执行模式悄悄重试的权限。

请停止并报告精确的失败、运行/状态以及仓库/cwd/工作树/分支/ref 状态。在进行同协议重试或询问所有者之前,请验证工作树干净或捕获部分差异。仅通过明确的同协议动作重试或修复 subagent 路径。对于待办泳道以及其他由子智能体管理的工作流,外部/前台/CLI 回退需要显式的所有者批准。当用户显式请求可见的前台/CLI 工作,或任务超出受管子智能体协议的范围时,interactive_shell 仍然有效。

Pi 核心可能打印通用的 pi -ne 扩展加载提示;该仓库外提示不是协议批准的回退。已配置的原生模型/提供方回退仍受其自身契约的约束,并且不会授权执行模式切换。

提示词快捷方式 ​

该包包含用于常见工作流的可重用提示词模板。你不需要它们,但当你想每次都使用相同的形式时,它们很方便:

提示词用途
/parallel-review启动具有不同视角的全新上下文评审者,然后综合需要修复的内容。
/review-loop运行父控制的 worker、评审者与修复 worker 循环,直到干净或达到上限。
/parallel-research组合 researcher 与 scout,用于外部证据、本地代码上下文以及实际权衡。
/gather-context-and-clarify先进行 scout/research,然后向用户询问重要的澄清问题。
/parallel-cleanup在实现之后运行仅评审的清理过程。

在 /parallel-review 或 /parallel-cleanup 上添加 autofix,以便在评审者返回后仅应用现在值得做的综合修复。

脚本化工作流(workflowScript) ​

对于一个有界的子智能体,请使用直接的 { agent, task }。当父会话需要稳定的带键子智能体、序列、扇出、引导、重试或聚合时,请使用 workflowScript。对于常规的并行扇出,请使用 await runs.all([{ key, agent, task }, ...])。它解析为有序数组,而非键映射,因此请使用索引、解构或 .map(...),而不是 results.<key>。不要从未被等待的 runs.run 启动中读取 .output。仅当脚本稍后通过 await、Promise.race 或 Promise.all 观察该 Promise 时(例如在等待结果之前引导一个活跃的子智能体),才存储一个 runs.run Promise。脚本是普通的 JavaScript 语句主体。请使用显式的 return 获得有用的结果:

对于多步或并行工作,请进行一次顶层 subagent 工作流调用,并设置 async:true,且仅在其内部启动子智能体。请阅读本指南获取配方,而不是构建第二个顶层编排。可用的沙箱辅助包括 runs.run、runs.all、runs.lanes、runs.steer、runs.status、runs.ref/runs.refs、emit、console、标准 JavaScript,以及启用时的任务记录 state。没有文件系统、shell、任意 Pi 工具或宿主全局变量可用;仅命名资源可以授予 runs.host 权限。

工作流级的子控件默认作用于每个 runs.run/runs.all 启动;显式的子字段会覆盖它们。后续挑战见保留子智能体,持久化产物见输出绑定,延迟/周期脚本见调度。

子结果以纯 JSON 数据的形式传递到脚本中。非 JSON 宿主元数据会被省略,因此请使用返回的字段,例如 runId、ok、output 与 structuredOutput 来进行工作流控制。

在不启动子智能体的情况下验证脚本:

js
subagent({
  action: 'validate',
  workflowScript: `
  const results = await runs.all([{ key: "scan", agent: "scout", task: "Scan" }]);
  return results[0].output;
`
});

对于存储在文件中的脚本,请使用 workflowScriptPath 而非 workflowScript:

js
subagent({ workflowScriptPath: 'workflows/review.js', cwd: '/path/to/project' });
subagent({ action: 'validate', workflowScriptPath: 'workflows/review.js' });

这些字段互斥。相对路径相对于请求的 cwd 解析;绝对路径原样传递。宿主会在验证、调度创建或工作流沙箱执行之前读取该文件。沙箱仍然没有文件系统访问。缺失、不可读与空文件会返回文件输入错误,而非脚本语法错误。

用于权限扩展的命名工作流资源 ​

当权限或策略扩展需要将"扩展解析的工作流内容"与"原始模型编写的脚本"区分开时,请使用命名工作流资源:

js
subagent({ workflow: 'review', args: { task: 'Review the change' } });
subagent({ workflow: 'run-ci', args: { command: 'npm test' } });

宿主会解析该名称,并在启动工作流之前验证有界的纯 JSON args。资源来源会被记录在工作流详情与回执中,以供下游的权限/策略检查使用。资源权限不是调用者提供的:runs.host 仅在已解析的资源明确授予所请求的宿主键与命令时可用。内联的 workflowScript 与 workflowScriptPath 仍是具有未知来源的原始输入,因此它们的 runs.host 调用在公共执行边界中不可用。命名资源不能与 agent、task、workflowScript 或 workflowScriptPath 组合;本次首次切片仅打包属于本扩展的 review 与 run-ci 资源,而非用户/项目资源注册表。

可选择启用的有界工作流 ​

复合工作流没有默认的父截止时间。仅当工作流合同时机需要它们时,才添加边界:

js
subagent({
  workflowScript: `
    const scan = await runs.run("scan", { agent: "scout", task: "Inspect the named files." });
    return runs.run("review", { agent: "reviewer", task: "Review:\n" + scan.output });
  `,
  timeoutMs: 900000,
  toolBudget: { soft: 40, hard: 60 },
  usageBudget: { tokens: { soft: 100000, hard: 150000 } }
});
  • timeoutMs 设置工作流截止时间,并将子截止时间限制在剩余时间内。
  • toolBudget 成为每个子智能体的默认值,除非该子智能体提供更窄的值。
  • usageBudget 跨已完成的工作流子智能体计算已报告用量。一旦耗尽,它会拒绝后续的子启动,但不会停止已在运行的子智能体。
  • 预算与超时停止会返回带有 state: "partial" 与原因 budget_exhausted 或 timeout 的结构化 terminalOutcome。工作流回执会保留已结算的子证据以供恢复。
  • 在异步工作流回执成功发布后,workflowReceiptPath 会在等待完成详情、完成通知以及精确的状态/调试详情中公开其精确路径。文本响应也会标识该回执。挂起的运行与失败的回执发布会省略该引用;较早的状态记录不会被回填。该引用记录的是发布,而非针对后续保留清理的保证。原始结果文件保留 workflowReceipt: { path, receipt }。

这些控件是可选择启用的。除非工作流具有显式的检查点与交接路径,否则不要为具有变更能力的 worker 设置紧张的硬性预算。

结果是 { ok, errors }。非法脚本会返回工具错误,并在可用时包含行号与列号数据。验证会检查语法、可移植的嵌套异步规则、字面量的 runs.run 与 runs.all 键及子 baseRef 值、一个 runs.all 组内的重复字面量键、对已知 runs.all 结果的直接键控访问,以及静态清晰的非 JSON 边界值。动态键与其他仅运行时的值会被接受而不会发出警告。验证不会发现智能体、启动子智能体或创建运行产物。

js
subagent({
  workflowScript: `
  const scan = await runs.run("scan", { label: "Map codebase behavior", agent: "scout", task: "Scan the codebase" });
  const reviews = await runs.all([
    { key: "correctness", label: "Review codebase correctness", agent: "reviewer", task: "Review correctness: " + scan.output },
    { key: "tests", label: "Review test coverage", agent: "reviewer", task: "Review tests: " + scan.output }
  ]);
  return reviews.map(result => result.output);
`
});

保持辅助函数在 Node 与 Bun 之间可移植。使用顶层 await、返回 runs.run(...) 的普通辅助函数,或显式的 Promise 链。不要在 workflowScript 内定义嵌套的 async function 辅助函数、async 箭头函数或 async 方法;原生异步辅助函数会在 Bun 中隐藏子启动观察,因此被拒绝。

js
subagent({
  workflowScript: `
  function scan() {
    return runs.run("scan", { label: "Map codebase behavior", agent: "scout", task: "Scan the codebase" });
  }
  const result = await scan();
  return result.output;
`
});

链式调用仍受支持。受支持的形式是脚本化链式调用:等待一个 runs.run(...) 结果,然后将其输出传递到下一步。并行扇出使用同一脚本内的 runs.all(...)。

js
subagent({
  workflowScript: `
  const plan = await runs.run("plan", { label: "Plan migration behavior", agent: "scout", task: "Plan the migration" });
  const patch = await runs.run("patch", { label: "Implement migration behavior", agent: "worker", task: "Implement this plan:\n" + plan.output });
  return patch.output;
`
});

并行顺序泳道 ​

对于一组有界的独立链,runs.lanes(...) 移除了原本连接每条泳道各阶段的机械循环。它是 workflowScript 内部的辅助函数,而非新的顶层 subagent 执行模式:

js
subagent({
  workflowScript: `
  const board = await runs.lanes([
    {
      key: "api",
      stages: [
        { key: "writer", label: "Implement API behavior", agent: "worker", task: "Implement the API change" },
        { key: "challenge", label: "Challenge API behavior", resume: "previous", task: "Challenge the API implementation" },
        { key: "review", label: "Review API behavior", agent: "reviewer", task: "Review the API lane" }
      ]
    },
    {
      key: "ui",
      stages: [
        { key: "writer", label: "Implement UI behavior", agent: "worker", task: "Implement the UI change" },
        { key: "review", label: "Review UI behavior", agent: "reviewer", task: "Review the UI lane" }
      ]
    }
  ]);
  return board.map((lane) => ({
    key: lane.key,
    state: lane.state,
    failedStage: lane.failedStage,
    stages: lane.stages.map((stage) => ({
      key: stage.key,
      state: stage.state,
      ok: stage.ok,
      runId: stage.runId,
      outputReference: stage.outputReference,
      verdict: stage.verdict
    }))
  }));
`
});

每条泳道的第一阶段在一次既有的 runs.all(...) 批次中启动。每条泳道中的后续阶段仅在前一阶段结算后才开始。具有 resume: "previous" 的后续阶段要求前一子智能体返回保留的 runId;辅助函数随后使用既有的保留恢复启动检查,并且不接受任意的运行 id。生成的子键使用 <lane>.<stage>,而返回的 board 使用局部的泳道与阶段键。

辅助函数在启动任何内容之前会验证完整的纯 JSON 泳道清单。它将清单限制为最多 32 条泳道、每条泳道 16 个阶段、共 64 个阶段,以及 64 KiB 的规范 JSON;任务与路径字段保留既有的 1 MiB 与 32 KiB 限制。阶段键在一条泳道内必须唯一,并且生成的键必须是唯一且有效的工作流键。一个子智能体失败、停止/分离的结果,或显式的 structuredOutput.verdict === "blocked" 仅阻塞其所在泳道;后续阶段被标记为 skipped,兄弟泳道继续运行。评审者的散文永远不会被解析。

board 是有界的,仅包含泳道/阶段键、状态、成功、保留的运行 id、显式的输出引用、有界的错误以及可选的结构化裁决。它不会返回子对话记录,也不会创建泳道注册表或清理权限。当工作流需要超出该辅助函数的条件式或滚动编排时,请使用原始的 runs.run(...)/runs.all(...)。

宿主命令步骤 ​

当权限/策略扩展需要将一个受支持的、非交互式命令作为工作流证据接纳,而非子智能体运行时,请使用命名资源 run-ci:

js
subagent({ workflow: 'run-ci', args: { command: 'npm test', timeoutMs: 120000 } });

第一个命名资源版本仅支持 npm test 与 npm run typecheck,并带有有界的超时值。其已解析的脚本使用 runs.host("ci", ...),并且该资源权限仅接纳所选的命令。没有按步骤的 cwd 字段:命令与相对输出路径使用工作流 cwd。当工作流应在另一个目录中运行时,请在外层 subagent({...}) 请求上设置 cwd。该命令没有 stdin,接收工作流 cwd,并且必须被等待或返回。stdout、stderr 与保存的日志都是有界的。非零退出、超时、中止或输出写入失败会使工作流失败。异步状态与终止回执存储有界的宿主步骤状态;渲染器不会运行命令或读取命令输出。

引导工作流子智能体 ​

在 runs.run 或 runs.all 已启动该稳定键之后,请使用 await runs.steer(key, message, options?)。脚本不会定向原始的运行 id。可选字段包括 mode: "steer" | "follow_up" | "auto"、非负的子 index 与正的 ackTimeoutMs。

js
subagent({
  workflowScript: `
  const writer = runs.run("writer", { agent: "worker", task: "Implement the change" });
  const evidence = await runs.run("evidence", { agent: "scout", task: "Find the exact contract" });
  const receipt = await runs.steer("writer", "Also check: " + evidence.output, { mode: "follow_up" });
  return { writer: await writer, receipt };
`
});

回执状态为 queued、delivered、missed 或 failed。对于异步子智能体,delivered 意味着它消费了关联的用户输入;对于前台子智能体,它意味着进程内 Pi 传输接受了输入。它并不意味着模型遵从了它。missed 意味着在投递之前,带键子智能体已变为终态或没有活跃路由。本次首次切片使用既有的前台与异步引导传输,但不启动引导恢复。工作流跟踪包含一条引导尝试条目与一条回执条目。

始终等待或返回 runs.steer Promise。工作流在等待观察到的引导副作用安定之后才会退出,并拒绝即发即弃调用。当首个子智能体或引导回执应推进脚本时,请使用普通的 Promise.race。没有回调 API 或子智能体收件箱访问。

高级滚动子运行 ​

当你调用 runs.run 时,它会启动一个带键的子智能体。对于滚动顾问团或分阶段评审,你不需要单独的 runs.start、runs.next 或 runs.collect 辅助函数。这是常规 runs.all 扇出的高级例外:仅当脚本稍后通过直接 await、Promise.race 或 Promise.all 观察每个 Promise 时,才保留已启动的 Promise。使用 Promise.race 等待下一个完成的子智能体,按其稳定键引导仍在运行的兄弟节点,并使用 Promise.all 收集其余的子智能体。

js
subagent({
  workflowScript: `
  let pending = [
    { key: "analysis-a", promise: runs.run("analysis-a", { agent: "reviewer", task: "Analyze option A" }).then((result) => ({ key: "analysis-a", result })) },
    { key: "analysis-b", promise: runs.run("analysis-b", { agent: "reviewer", task: "Analyze option B" }).then((result) => ({ key: "analysis-b", result })) },
    { key: "critic", promise: runs.run("critic", { agent: "reviewer", task: "Find the strongest objection" }).then((result) => ({ key: "critic", result })) }
  ];

  const first = await Promise.race(pending.map((child) => child.promise));
  pending = pending.filter((child) => child.key !== first.key);

  const target = pending.find((child) => child.key === "critic") ?? pending[0];
  const receipt = await runs.steer(target.key, "Challenge this early result:\n" + first.result.output, { mode: "auto" });
  const rest = await Promise.all(pending.map((child) => child.promise));

  return { first: first.result.output, rest: rest.map((child) => child.result.output), receipt };
`
});

工作流跟踪会记录运行完成与引导回执。脚本仍然不会看到原始的异步目录、收件箱路径或会话文件。如果在 runs.steer 运行之时,带键子智能体已为终态、陈旧或没有活跃路由,则回执会报告 missed 或 failed,脚本可以决定是否继续。

当后续工作流步骤需要结构化数据或持久引用时,请使用命名输出:

js
subagent({
  workflowScript: `
  const inventory = await runs.run("inventory", {
    agent: "scout",
    task: "List the files that need review.",
    outputSchema: {
      type: "object",
      properties: { files: { type: "array", items: { type: "string" } } },
      required: ["files"],
      additionalProperties: false
    }
  });
  return runs.run("review", {
    agent: "reviewer",
    task: "Review these files: " + inventory.structuredOutput.files.join(", ")
  });
`
});

对于动态扇出,请让一个步骤返回结构化列表,在 JavaScript 中检查它,然后将受约束的条目映射到 runs.all(...) 中:

js
subagent({
  workflowScript: `
  const targets = await runs.run("targets", {
    agent: "scout",
    task: "Return up to five source files that need review.",
    outputSchema: {
      type: "object",
      properties: { files: { type: "array", items: { type: "string" }, maxItems: 5 } },
      required: ["files"],
      additionalProperties: false
    }
  });
  const files = targets.structuredOutput.files.slice(0, 5);
  return runs.all(files.map((file, index) => ({
    key: "review-" + index,
    agent: "reviewer",
    task: "Review " + file
  })));
`
});

对于仅后续步骤需要的中间数据,请优先使用先前子智能体返回的输出或 structuredOutput,而非编写共享文件:

js
subagent({
  workflowScript: `
  const scan = await runs.run("scan", { agent: "scout", task: "Find the files that need fixes." });
  return runs.run("fix", { agent: "worker", task: "Implement these findings:\n" + scan.output });
`
});

{chain_dir} 在脚本化工作流步骤模板中仍然可用,以便与旧路径模板兼容。它会展开为工作流 cwd,而非私有临时存储。

迁移旧链式形式 ​

旧版的顶层 chain、tasks、parallel、chainDir、/chain、/parallel、/run-chain 与持久化 .chain.md 执行不再是公共工作流 API。请将它们重写为 JavaScript:

js
// 旧形式,不再受支持:
// { chain: [{ agent: "scout", task: "Scan" }, { agent: "worker", task: "Fix from {previous}" }] }

// 当前形式:
{
  workflowScript: `
  const scan = await runs.run("scan", { agent: "scout", task: "Scan" });
  return runs.run("fix", { agent: "worker", task: "Fix from: " + scan.output });
`;
}
js
// 旧形式,不再受支持:
// { tasks: [{ agent: "reviewer", task: "Review API" }, { agent: "reviewer", task: "Review UI" }] }

// 当前形式:
{
  workflowScript: `
  return runs.all([
    { key: "api", agent: "reviewer", task: "Review API" },
    { key: "ui", agent: "reviewer", task: "Review UI" }
  ]);
`;
}

对于带有 Markdown 围栏或 shell 块的长任务文本,请使用引号引起来的行,而非原始模板字面量:

js
const task = ['Run this command:', '```bash', 'npm test', '```'].join('\n');
return runs.run('test', { agent: 'worker', task });

一个普通的工作流默认会创建一个封闭的任务记录。它的子智能体不会创建单独的任务记录。结果会将 id 公开为 details.missionId,人类可读的输出以 Mission: <id> (<status>) 结尾。对于没有任务记录或持久 state 全局的临时工作流,请传递 mission:false。

可重复的工作流 ​

使用稳定的子键,并将过程逻辑保留在普通 JavaScript 中。runs.run 启动一个子智能体,runs.all 一起启动独立的子智能体,后续步骤可以使用每个已完成子智能体的 output。将长任务文本放在以 "\n" 连接数组中,以便 Markdown 围栏不会与脚本字符串冲突。

对于经常运行的过程,请将任务保存为 .pi/prompts/ 或 ~/.pi/agent/prompts/ 下的提示词模板,并使用 /prompt-workflow 启动它。该适配器将提示词步骤编译为 workflowScript,因此模板描述的是工作,而非嵌入原始的 subagent 工具调用 JSON。你可以要求父智能体从自然语言描述的过程中创建或更新这些提示词文件。

md
---
description: Review a release candidate
subagent: reviewer
fresh: true
---

Review $@. Return concrete findings with source proof, or state that no issue was found.

对于首轮评审提示词,请按证据而非按严重程度进行过滤。请要求评审者将具体的当前发现标记为 P0/P1/P2,并以 Merge verdict: BLOCK、Merge verdict: OK 或 Merge verdict: OK with notes 结尾。将 blockers only 保留给最终合并前的复检(在 P1/P2 发现已知之后),或显式紧急修复泳道。

text
/prompt-workflow review-release-candidate v0.51.0

对于被监视的同仓库工作流,仅在父会话必须阻塞直到完成时,才传递 async:false。该阻塞模式还会显示聊天中的实时工作流卡片。当自动策略不是你所希望的方式时,chatProgress 可以强制 off 或 live-card。阻塞工作流默认为 30 分钟超时;异步工作流没有默认超时。完整参数列表见工具参考。

同步工作流通过工具更新回调发布跟踪与 emit(...) 更新,无论 chatProgress 如何,包括 RPC/无头与跨仓库运行。这些更新包括 details.workflow 与 details.workflowChildren;chatProgress: "off" 禁用实时卡片,而非传输进度。运行中的前台子行额外地公开有界的 activity(当前工具、计时与计数器),以及可用时的已解析模型/思考,按 childId 键控。仅活动更新会在 100 ms 内合并;生命周期更新保持即时。活动在子智能体结算时被清除,并且不会为异步工作流持久化。工具名称限制为 256 个 UTF-8 字节,且每个活动对象小于 2 KiB(包括 JSON 转义);参数与对话记录不会被转发。

旧版的 /chain、/parallel 与 /run-chain 命令未注册。

直接命令 ​

对于一个子智能体,请使用 /run <agent> [task] [--bg] [--fork]。

工作树隔离 ​

脚本化工作流可以通过在每个 runs.run / runs.all 条目上设置 worktree: true,为每个写作者子智能体提供单独的托管 Git 工作树:

javascript
const [api, ui] = await runs.all([
  { key: 'api', agent: 'worker', task: 'Implement the API', worktree: true },
  { key: 'ui', agent: 'worker', task: 'Implement the UI', worktree: true }
]);
return { api: api.artifactPaths, ui: ui.artifactPaths };

每个子智能体都使用既有的工作树生命周期:从干净的 HEAD 创建分支,在启动前记录所有权,捕获补丁与交接清单,然后清除干净捕获的临时工作树与分支。交接清单路径在该子智能体的 artifactPaths 中保持可用;当编排者需要应用或检查补丁时,请返回或发出它。runs.ref 保持简洁,并有意省略完整路径。

顶层 { workflowScript, worktree: true } 会使隔离成为每个工作流子智能体的默认值。单个子智能体可以使用 worktree: false 覆盖该默认值。当并行写入并非有意隔离时,请保持单一写作者。

在已物化的 runs.run 或 runs.all 组派发新的子智能体之前,隔离源必须是具有干净工作树(不包括 .pi/subagents/ 运行时状态)的 Git 仓库。被拒绝的组不会派发任何子智能体,也不会消耗任何扇出槽位或子输出声明;键级失败跟踪可以保留。检查仅在该组内共享,可以取消,并且会在分配时再次运行,因为源可能发生变化。保留的恢复会保留其存储的契约。请选择正确的 cwd 或安排运维人员批准的提交/stash;隔离永远不会被自动放弃。

使用 baseRef 将托管工作树从 HEAD 或受支持的命名 ref(如 refs/heads/release、refs/tags/v1 或 origin/main)分支。完整的 40/64 字符提交 ID 以及像 HEAD~1 这样的修订表达式不受支持。例如,{ workflowScript, worktree: true, baseRef: "refs/heads/release" } 会将该发布 ref 应用于子智能体,除非子智能体提供自己的 baseRef。如果省略,则默认的 HEAD 在工作树分配时解析,而非在脚本验证或调度创建时解析。源检出仍必须干净,并且该 ref 必须在分配任何工作树之前解析为提交。

在工作树提供方、原生路径布局、基础目录与设置钩子的配置见 configuration.md。

设置等待仍是非阻塞且可取消的。常规清理(包括分离的前台终结化)等待同一进程内的设置轮次,而非仅因另一设置处于活跃状态而保留工作树。这不是跨进程锁。钩子必须遵循有限设置契约。

泳道元数据生命周期 ​

工作流子智能体可以声明一个有界的 lane 对象(version、key、可选的 mode、不透明的 sourceRef、咨询性的 claims,以及咨询性的 outputPaths)。泳道键必须与 runs.run/runs.all 工作流键匹配。这些字段仅作为显示与分流提示:它们不授予工具、授权或清理权限,并且 sourceRef 在渲染状态时永远不会被网络解析。复制到状态中的工作树路径与分支同样仅作为显示;交接清单仍是删除权限。

持久化文件所有者待处理/运行中/已终结/清理状态释放条件回滚条件陈旧头处理失败闭合场景
status.json异步运行器与工作流状态投影器子步骤以 pending 启动,变为 running,然后是终态 complete/failed/paused/stopped;工作树路径与分支在启动时复制状态为终态,且既有的活跃运行/进程证明可以释放运行标记;仅泳道元数据永远不会释放工作树设置或持久化失败使泳道保持未知;仅既有的已验证设置回滚可以移除一个新创建的工作树已记录的状态会被保留;基础/头不匹配不会被修复,也不会从渲染时的 Git 调用中推断缺失、格式错误或键不匹配的泳道数据;仅 worktreePath/branch 之一;未经验证的进程状态
handoffs/<run-id>.json既有的并行交接写入器与清理引擎在挂起/运行时,组为 partial,并保留清理任务;已终结的组包含子身份、补丁与清理证据;清理为 partial 或 complete仅既有的清理引擎的新鲜 Git 检查与已记录的任务证据可以释放工作树/分支;#1621 不添加任何删除路径缺失差异、捕获失败或清理错误会保留任务并记录原因baseCommit 作为证据被保留;陈旧或已变化的头保持未知/保留,直至显式的后续对账缺失/非法的清单、不匹配的运行/键/任务身份、重复的身份、脏或未捕获的工作
workflow-receipt.json工作流终态结算在 pending/running 时无回执;终态回执会与每个带键子智能体的一个可选泳道块一同确定仅在每个已包含的子条目被序列化后,回执发布才视为完成;它不会授权清理回执写入失败会使状态/交接证据保持权威,工作流会报告缺失的回执既存的回执不会被回填或从较新的头重写非法的版本/状态、不匹配的条目键或泳道键、陈旧的续接血统
.active-runs 标记既有的活跃运行索引运行器存活时为 pending/running;终态标记会保留,直至观察到进程证明标记移除需要既有的精确运行进程终止证明未知的证明会使标记与泳道保留以供检查标记状态不会仅从 Git 头或时间戳推断缺失/未知的进程证明、活跃的标记或外部运行身份

没有泳道元数据的较旧运行仍然可读,并保留其既有的交接/清理行为。缺失的泳道、回执或交接元数据是未知的——不符合破坏性清理的条件。

托管设置会在交接中记录实际的分配尝试;仅经验证的分配会成为清理任务与仅显示的状态路径/分支。在未知结算的取消或失败时,它会保留实际的/尝试的所有权证据与产物,以便手动对账,阻止该进程中进一步不安全的设置与清理。在报告其路径之前被中断的分配器可能仅留下分支诊断,绝不会留下虚构的路径。请在对账之前检查交接;清理仍需要新鲜的检查。

监督者协调(子询问父) ​

子智能体无需安装 pi-intercom 即可与父 Pi 会话对话。pi-subagents 以原生方式提供面向子智能体的 contact_supervisor 工具与面向父会话的 subagent_supervisor({ action: "reply" }) 路径。通用的 intercom 仅在显式加载的外部提供方提供时可用。

将其用于子智能体可能需要决策而非猜测的工作:

text
Run this implementation in the background. If the worker gets blocked or needs a product decision, have it ask me through the supervisor channel.
text
Ask oracle to review this plan. If it sees a decision I need to make, have it ask me instead of assuming.

子智能体使用一个专用协调工具 contact_supervisor,并附带 reason:

  • need_decision — 阻塞性决策或澄清
  • interview_request — 结构化输入
  • progress_update — 当发现改变计划时的短小非阻塞更新

当唯一冲突是仅评审/无编辑与进度写入或产物写入指令时,子智能体不应请求澄清;无编辑胜出。

父会话使用 subagent_supervisor({ action: "reply", replyTo, message }) 回复,或使用 subagent_supervisor({ action: "pending" }) 检查待处理的请求。监督者消息的作用域为恰好派生子智能体的 Pi 会话 id。同一仓库中的第二个 Pi 会话不会接收这些请求。

嵌套协调者需要双向协调。如果其智能体声明了显式的 tools 白名单,请包含 subagent_supervisor 以应答其自身的子智能体,与 subagent(用于委派)和 contact_supervisor(用于询问其父)一同:

yaml
tools: read, subagent, contact_supervisor, subagent_supervisor

对于 A → B → C,C 的请求属于 B,而非 A。B 可以使用 contact_supervisor 将一个单独的问题上报给 A,然后使用 C 的原始 replyTo 请求 id 回复 C。A 对 B 的回复不会解决 C 的请求,steer 也不能替代回复。仅扇出授权的子智能体会获得向下的监督者提供方;显式的工具排除与能力上限仍然适用,且普通的叶子节点不会获得委派或回复工具。在没有扇出授权的情况下请求 subagent_supervisor 会在启动时以一条可操作的错误失败。排除回复工具的协调者不会启动向下监督,也不会收到使用它的提示。显式选择的原生协调工具会经受宿主内置过滤,因为它们的提供方是子运行时钩子,而非宿主内置工具。

不期望子智能体侧的常规完成交接。如果子智能体看似停滞,需要关注的通知会出现在父会话中,并附带有用的下一步,例如检查 subagent({ action: "status" })、中断运行或轻推子智能体。

如果 workflowScript 子智能体通过 contact_supervisor 分离,封闭的异步工作流会保持 paused,直到该子智能体退出。然后扩展会将其对账为 complete 或 failed。请在该子智能体上等待,直到发生这种情况。

如果消息不显示,请运行 /subagents-doctor。高级用户可以使用 configuration.md 中的 intercomBridge 调整桥接。

递归防护 ​

子智能体仅在其已解析的内置工具明确包含 subagent 时才能调用 subagent。这旨在用于委派的扇出智能体,而非普通的 worker/评审者子智能体。深度防护可防止无界嵌套。

默认情况下,嵌套被限制为两层:主会话 → 子智能体 → 子智能体的子智能体。更深的调用会被阻止,并附带直接完成当前任务的指引。嵌套运行以树的形式出现在父状态小部件与 status 输出中,并且 status、interrupt 与 resume 可以按其 id 定向到嵌套运行。

通过以下方式配置限制:

  1. 在启动 Pi 之前设置 PI_SUBAGENT_MAX_DEPTH
  2. 设置 config.maxSubagentDepth
  3. 在智能体 frontmatter 中设置 maxSubagentDepth,它只能收紧继承的限制
bash
export PI_SUBAGENT_MAX_DEPTH=3
export PI_SUBAGENT_MAX_DEPTH=1
export PI_SUBAGENT_MAX_DEPTH=0

PI_SUBAGENT_MAX_DEPTH 适用于顶层父会话;子智能体通过其运行时配置继承其限制,并且其自身的深度也在那里被追踪。

提示词模板集成 ​

pi-subagents 包含一个原生提示词工作流适配器,用于可重用的子智能体提示词模板,因此你不需要 pi-prompt-template-model 即可覆盖常见的子智能体工作流路径。

在 .pi/prompts/ 或 ~/.pi/agent/prompts/ 中创建提示词:

md
---
description: Take a screenshot
model: claude-sonnet-4-20250514
subagent: browser-screenshoter
cwd: /tmp/screenshots
---

Use url in the prompt to take screenshot: $@

然后通过原生适配器运行它:

text
/prompt-workflow take-screenshot https://example.com

该适配器委派给命名的子智能体,应用 model、skill、cwd 与 fork/fresh 上下文元数据,并支持运行时覆盖,例如 --subagent reviewer、--fork、--fresh 与 --bg。

带有 chain: frontmatter 的提示词模板会被翻译为 workflowScript,并通过 /prompt-workflow 启动;/chain-prompts 不再注册。