基于 Pi 的多智能体编程工作流

单个编码智能体在工作时,其上下文会逐渐变成一整项任务中所有被放弃的想法、文件搜索、实现尝试和测试失败的记录。更好的模型能推迟这种失效的发生,但无法彻底消除。

我使用 Pi 作为宿主来承载一种不同形态的工作流:一个主智能体协调十名专家、分派隔离任务、审查证据,并对最终结果负责。这个配置最初只有一个同步的 task 工具,现在同时支持一次性任务和持久化的、可实时操控的 RPC 工作进程。

完整的配置已公开在 github.com/bskimball/pi。仓库中包含了本文介绍的智能体定义、主提示词、扩展、技能和恢复指令。密钥和与机器绑定的文件已排除在外。

有价值的部分不是那些角色扮演的名字,而是流程边界。

这是我日常使用的界面的历史记录截图,截取于后续从 Mono 重命名为 Apex 之前。成功和失败的工具调用、耗时、活跃的 oracle 任务、token 流量、模型状态、MCP 连接和任务计数均清晰可见,且无需将子任务的完整对话记录注入主智能体的上下文。

1、主智能体保护自身上下文以进行判断

当一个智能体掌管一个实质性变更的所有阶段时,其上下文窗口会变成一个杂物抽屉:

  • 调查笔记挤占了当前编辑的空间
  • 失败的调试分支影响了后续决策
  • 实现细节取代了原始需求
  • 审查变成了对实现者自身推理过程的复读
  • 发布命令混入了设计与架构的讨论

我的主系统提示词将主智能体定义为编排者。它仍然会直接处理一些小任务,但任何会消耗集成和判断所需上下文的工作,都会被委派出去。

分工原则是明确的:

  • 内联执行:针对一个已知文件、一个小修改或一个直接回答
  • 委派执行:针对多文件工作、大范围调查、复杂的 UI 工作或疑难调试
  • 并行执行:仅在任务单元相互独立时使用
  • 串行执行:当任务单元涉及相同文件或依赖相同决策时使用

委派并不意味着转移对用户成果的所有权。主智能体负责撰写工作指令、检查返回的证据、协调分歧、运行综合验证,并给出最终答案。

这个区别很重要。没有它,子智能体就变成了一种迂回的方式来表达"别人说通过了"。

2、十名专家取代了一个过载的巨型提示词

每位专家都由一个带有 YAML frontmatter 的 Markdown 文件定义。frontmatter 控制其模型路由、备选模型、思维深度、工具配置、技能继承和轮次预算。Markdown 正文则是该角色的系统提示词。

专家名单被刻意精简:

  1. advisor:在实现前评估关键方法和权衡。
  2. artisan:负责大规模 UI、布局、视觉层次和交互细节。
  3. inspector:执行快速、只读的浏览器验证:截图、响应式检查以及实现后的针对性视觉回归测试。
  4. librarian:研究外部库、框架内部机制、文档和参考实现。
  5. machinist:处理具体的非视觉实现,如后端逻辑、重构、迁移和缺陷修复。
  6. oracle:提供独立评审意见,或在疑难调试中提供第二意见。
  7. picasso:根据视觉需求生成图像资源。
  8. scout:在本地代码库中执行快速、只读的侦察。
  9. scribe:撰写和修改长篇技术内容。
  10. stevedore:处理有边界的运维工作,如构建、git 操作和平台 CLI。

名字让终端输出更易扫描,但工具边界才是真正起作用的部分。Scout 不需要编辑权限。Librarian 不应临时修改本地代码。Artisan 和后端开发者不能因为都会写 TypeScript 就互换。Inspector 仅在浏览器中验证渲染结果;它不能编辑代码,因此它发现的缺陷会路由回 artisan 或 machinist。Oracle 之所以有价值,是因为它从一个全新的进程中检查实际的 diff,而不是因为它产出一段仪式性的批准段落。

3、拓扑结构保持星形

系统只有一个协调者。工作进程不会发展出自己的工作进程工厂。

一个协调者,三条派发路径。工作进程永远不生成工作进程。

子进程接收的是其角色提示词和一个自包含的工作指令,而非父进程的对话历史。任务工具被排除在子进程运行时之外,因此升级交回主智能体,而不是创建一个无限增长的进程树。

这也让共享状态的决策保持在一处。工作进程可以报告一次推送、部署、删除或迁移已准备就绪。但决定是否执行该操作的责任,始终在主智能体手中。

4、自包含的工作指令就是共享记忆

一个新的上下文窗口只有在提示词包含足够上下文时才有帮助。我的工作指令遵循一种刻意保持简单的结构:

  • 目标:用户可见的结果
  • 范围:文件、目录、行为和非目标
  • 上下文:已有的约束和决策
  • 任务:具体的实现、调查或评审请求
  • 证据:需要首先检查的文件、命令或文档
  • 验证:最小范围的有效检查
  • 返回格式:变更的文件、发现、测试结果、阻塞项和残留风险

主智能体就是那块共享黑板。一个持续记忆扩展为我提供了 memory_list 和 memory_write 用于持久化笔记,分为仅限当前会话作用域的本地记忆和跨会话共享的全局记忆。它每轮都会将该记忆的紧凑概览注入主智能体的系统提示词。这些记忆是显式且手动的,而非自动的:我会有意识地写入。工作进程不会继承主智能体的对话或其会话级本地记忆;任何加载了该扩展的子进程,只能获得全局跨会话记忆的独立视图。由于该全局记忆是补充性和手动性的,我仍然不能假设它包含某个任务的需求,因此不存在能让子进程恢复其提示词中遗漏的需求的隐式通道。

这个约束也改善了主会话。如果我无法清晰地解释一个委派单元,很可能我还没有清晰地分解问题。

5、同步任务是一次性的排队任务

最初的编排路径仍然有用。同步路径启动一个全新的 pi --mode json --no-session 进程。它会发现选定的智能体、验证工作目录、注入专家提示词、将 JSON 事件流式传输到终端 UI,并将最终报告返回给主智能体。JSON 模式仅适用于同步的 task 运行器。

同步任务有严格的三并发上限。额外的调用会排队,直到有空位。

此模式适用于结果形态已知的有界任务:

  • 映射一个子系统并返回相关文件路径
  • 审查一个 diff 而不编辑它
  • 实现一个隔离的更改并运行有针对性的测试
  • 研究一个库的行为并引用来源

代价是控制力。同步任务一旦派发,就无法在中途调整。如果任务描述有误,主智能体只能等待任务完成或超时,读取结果,然后重新启动一个范围更精确的任务。

同步运行器可以在智能体配置的备选模型中按顺序尝试,以应对模型调用失败或进程/结果失败。任务中止、超时或轮次上限终止会停止尝试循环,而不是触发另一次回退。UI 记录当前使用的模型以及是否需要回退,而主智能体收到的是有界的报告,而非子进程的完整对话记录。

6、持久化 RPC 工作进程使委派变得可交互

有些工作需要比"一次提示、一次报告"更长的协作关系。异步任务不使用 JSON 模式。task_start 启动一个带有隔离的、会话支持目录的 pi --mode rpc 进程,然后通过 Pi 的 RPC 协议与子进程通信。这暴露了一个持久化的生命周期,而非单次调用:

  • task_start 启动一个专家并立即返回一个工作进程句柄
  • task_status 显示有界的生命周期、活动状态、错误和待处理的交互
  • task_list 显示活跃的和最近结束的工作进程
  • task_send 排队发送操控指令、任务结束后的后续指令或新的提示词
  • task_wait 阻塞直到当前生成结束
  • task_abort 请求工作进程停止,如果它不配合则升级处理
  • task_close 回收进程并释放其并发槽位
  • task_reply 应答子进程的 UI 检查点

异步运行器有其独立的三个活跃工作进程上限。与同步队列不同,当三个槽位都占满时,task_start 会直接拒绝。一个已结束的工作进程仍然占用其槽位,直到主智能体调用 task_close。

这种显式的清理不是装饰性的。持久化工作进程保留了有用的状态,但状态有其生命周期和成本。忘记关闭工作进程,就等同于编排层面的文件描述符泄漏。

操控也比名字暗示的更精确。它不会在模型推理进行到一半时打断,也不会取消一个正在运行的工具调用。操控消息会等待到下一个模型调用边界。后续消息会等待工作进程完全结束。task_abort 是用于真正需要停止工作的控制指令。

异步路径现在是我处理实现、不确定的调查,以及任何可能在首次结果后需要修正的工作的默认选择。同步路径仍然更适合那些持久化反而会成为开销的短时、确定性查询。

7、UI 检查点跨越进程边界

RPC 工作进程引入了一个同步运行器无需解决的问题:子进程的扩展可以向用户提问。

Pi 扩展可以请求一个选择、确认、输入或编辑器对话框。一个隔离的子进程无法直接在父会话中显示该交互,因此异步运行器会将该请求记录为一个待处理的检查点。主智能体通过 task_status 查看它,并通过 task_reply 应答,后者将对应的 RPC 响应发送回子进程。

这是一个小功能,却有巨大的架构影响。它意味着持久化工作进程可以使用交互式扩展,而无需假装所有决策在启动时就已知。检查点仍然由主智能体中介,因此星形的所有权模型保持完整。

8、并行工作需要明确的所有权,而非乐观态度

最重要的并行规则不是并发限制,而是每个工作树只有一个写入者。

只读智能体可以并行调查。Librarian 可以在 Scout 映射本地调用点的同时研究一个 API。审查者可以检查相同的文件,因为他们不会修改它们。

写入者则不同。两个智能体同时编辑同一个工作树,会导致彼此的读取失效、覆盖附近的更改,并产出一个双方都未实际测试的结果。并行写入者需要具有不重叠所有权的隔离工作树和一个显式的集成步骤。

大多数情况下,串行化比巧妙的冲突管理更便宜。我使用并行扇出来收集独立的证据,而不是将其作为智能体能力的默认展示。

当所有权明确时,并行化才最有价值。此处 oracle 在审查 diff 的同时,stevedore 在检查构建、分支和发布状态。

9、模型策略属于角色定义的一部分

主智能体在派发工作时通常不会覆盖模型。每位专家都已经有了一个主模型、有序的备选列表、思维深度和工具预算,这些都是为该角色选择的。

Scout 可以使用更廉价的模型,因为它只返回路径和发现。Artisan 需要适合视觉判断的模型。Machinist 需要足够的轮次来实现和验证一个具体的单元。这些默认值是配置的一部分,而非主智能体应在每次调用时重新考虑的选项。

Oracle 是例外。审查只有在审查者至少和编排者一样有能力处理所判断的问题时才有意义。这可能意味着在同一个模型族中提高思维深度,也可能意味着切换模型族以挑战相关的盲区。新奇不是目标,独立判断能力才是。

主智能体仍然会评估审查结果。新的上下文可以防止一种偏见,但它不会让审查者自动变得正确。

10、自定义的 Apex UI 是一个编排控制台

原生终端是围绕单个智能体调用工具设计的。当我有多个专家在运行时,呈现层就变成了编排系统的一部分。将每个子进程的 token 流式传输到主会话会破坏上下文隔离,但将工作进程折叠成一个旋转加载指示器又会隐藏太多信息。Apex UI 介于两者之间。

它的基本单元是工具收据。折叠状态的收据在一行内显示操作事实:状态符号、工具名称、主要参数、可选的统计信息和耗时。下方的一小段预览栏显示有用的输出。展开收据会显示一个有界的主体,而不会改变工具本身的执行方式。

同一个收据引擎呈现内置工具,如 read、bash、edit 和 write,以及网络搜索和 MCP 调用。当一次轮次混合了本地文件读取、Exa 搜索结果、平台 API 调用和失败的 shell 命令时,这种一致性非常重要。我可以扫描一种视觉语法,而无需为每个扩展解码不同的渲染器。

编辑会获得特殊处理。展开的结果显示带行号的上下文 diff,包含行统计信息和行内高亮。写入操作会在可能时捕获之前的文件内容,因此替换文件仍然会产生真实的前后对比 diff,而不是一条通用的成功消息。

任务呈现取决于执行模式:

  • 同步的 task 调用会获得一个丰富的任务卡片,包含专家、模型、思维深度、轮次、活动树、回退状态和最终报告
  • 异步 RPC 工具有更轻量的状态和结果视图,因为持久化的生命周期控制比重放完整的任务卡片更重要
  • 待处理的子进程对话框会作为检查点保持可见,直到主智能体通过 task_reply 应答

页脚是第二层信息。左侧锚定工作目录和分支,右侧显示模型和思维深度,并在终端宽度允许时填充 token 流量、缓存率、活跃任务计数、MCP 状态和 VS Code 状态。随着窗口变窄,字段按优先级降级显示,而不是换行成一个难以阅读的仪表盘。输入框保持了 Pi 的编辑器布局,而自定义的边框、提示符图形和旋转的工作指示器使当前状态更易定位。

所有大输出都被行数和字符数限制。渲染器使用宽度感知的回退机制,因为一个在窄 Windows 终端上崩溃的漂亮收据并不能算改进。渲染器故障会写入 pi-render.log 并降级为短文本。PI_APEX_UI=0 可以完全禁用该层,以便将界面 bug 与智能体或工具故障区分开。

Apex 还拥有一个着陆身份:一个名为 Observatory 的启动画面,在新聊天时显示一次,其中的鲨鱼标志是绘制而非照片,还有一个由工作区路径决定形状、密度反映上下文使用情况的星场。鲨鱼之所以出现,是因为我儿子喜欢鲨鱼,让它出现在我构建的东西里感觉很合适。它只是一个启动画面,而非状态指示器,其展示的专家名单目前限于九位而非全部十位。压缩处理仍然使用 Pi 自身的内置旋转指示器,活跃的异步工作进程通过正常的任务状态卡片显示,而非任何鲨鱼动画。

收据和对话记录之间的区别是主要的设计决策。我需要知道一个工作进程检查了四个文件、修改了两个、遇到了一个失败的命令并通过了针对性的验证。我通常不需要它在到达那里时生成的每一个中间句子。

11、重复的工作流变成了命令和本地工具

配置也超越了智能体派发。

/browser 和 /deploy 现在是原生扩展命令,而不仅仅是可复用的提示词文件。它们在将控制权交还给模型之前执行确定性的设置。浏览器命令连接到一个专用的调试配置文件并注入相关的操作指令。部署命令捕获工作树的事实,并为 stevedore 构建一个有界的交接。

/orchestrate 也是原生的。它切换严格的编排者模式,on 或 off,并在整个会话期间保持该设置。开启后,主智能体失去了默认内联执行的权限:所有实质性的实现和大范围调查都路由到专家,主智能体的任务缩减为分解请求、撰写工作指令、整合结果和验证结果。直接的 rg 查询、主智能体运行的综合验证以及对返回 diff 的简单机械修正仍然允许。我在需要强制委派纪律的大型更改中会使用它,而不是依赖自己在当下的分诊判断。

brainstorm 仍然是一个提示词层面的模式转换,因为它的职责是行为层面的:生成多样化的选项,在用户选择方向之前不实现。

一些本地工具为日常使用提供了补充,但不值得单独成章。

todo_write 和 todo_read 维护一个显式的会话计划。lsp 为智能体提供语义导航(定义、引用、符号、诊断),而不仅仅是基于 grep 的搜索。一个专用的 PowerShell 工具直接运行 Windows 原生脚本和服务工作,与可移植的 bash 默认值分开。一个读取/图像结果守卫阻止对未更改图像的重复读取,并在过大图像消耗上下文之前对其进行缩放。

长时间运行的 shell 命令使用一个单独的后台进程扩展(bg_start、bg_status、bg_list、bg_kill),这样开发服务器和文件监视器就不会一直占用前台工具调用。

外部研究通过本地的 web_search、fetch_content 和 get_search_content 工具提供,底层由 Exa 支持。抓取结果会被缓存,这样模型可以请求有界的内容片段,而不是将整页内容塞进一个结果中。

MCP 支持也在本地组合。适配器和 Apex 的 MCP 收据包装器共享适配器扩展的 ExtensionAPI,因此服务器的工具能获得与其他所有工具相同的收据样式。我没有在扩展列表中独立加载 MCP 包;在本地组合之外这样做只会启动第二个、不一致的 MCP 集成。两个技能,mcp-scripting 和 mcp-scripting-recipes,涵盖了脚本契约和安全的、与服务器无关的组合模式。

12、实现与审查形成闭环

该工作流将审查视为实现的一部分,而非可选的打磨。

一个微小的内联更改可以从主智能体获得集中的审查。委派的代码、多文件更改、复杂逻辑和安全敏感的工作会进入一个新的上下文审查循环:

  1. Machinist 实现非视觉代码,或 artisan 实现面向用户的界面。
  2. Oracle 检查实际的文件和 diff,而不仅仅是工作进程的摘要。
  3. 如果 oracle 发现一个有效的问题,原始工作进程在仍保留实现上下文的情况下进行修复。
  4. Oracle 再次审查修改后的 diff。
  5. 主智能体会重复此循环,直到发现被解决、被判定为不正确,或被标记为明确的阻塞项。
  6. 最终修复后运行相关的验证。

审查是实现的一部分。问题被回传;通过的工作向前推进。

实现者和审查者保持不同的职责。Machinist 或 artisan 负责修复。Oracle 负责独立的判断。主智能体决定一个发现是真实的、推测性的、已覆盖的,还是超出请求范围的。

实践中的实现循环:oracle 审查发现一个具体问题,主智能体在下一次审查前立即规划修正性的编辑。

对于更高风险的更改,主智能体可以使用不同的强力模型运行额外的 oracle 审查轮次。这不是一个两票自动胜过一票的投票。当另一个模型族可能发现不同的失败模式,或首次审查留下相互矛盾的证据时,更换模型才有意义。每个用于最终关卡的 oracle 仍然需要至少和编排模型一样有能力处理所审查的问题。

最终答案会说明通过了什么、失败了什么以及未运行什么。

我还在主提示词中编码了委派和验证关卡。如果主智能体亲自扫描半个代码库而不是使用 scout,或者亲自实现一个实质性的多文件单元而不是分配给合适的专家,它需要一个具体的原因。这保护了协调上下文,避免了系统旨在避免的那种行为。

13、崩溃日志和被忽略的密钥让运维保持枯燥

一个崩溃记录器扩展捕获未处理的异常、未处理的 Promise 拒绝、流错误和非零退出码,并附带进程元数据。正常的零退出关闭不会被记录;该文件是故障记录,而非会话日志。环境标记区分主 Pi 会话和命名的子智能体。该记录器是尽力而为的,因为一个导致第二次致命错误的诊断钩子比没有记录器更糟。

配置仓库是可移植的,但凭证和本地运行时状态不属于可移植层的一部分。身份验证、提供商配置、MCP 密钥、网络搜索密钥、会话、运行历史、日志、缓存、信任状态、浏览器配置文件和持续记忆笔记都会被忽略。

智能体名单、提示词、扩展和安装元数据属于 git。密钥和与机器绑定的状态不属于。

14、随着系统改进,权衡变得更加尖锐

这个设置比最初的单工具版本更强大,但也有更多失败的方式。

两套编排 API 需要判断力。 同步任务更简单。持久化工作进程更容易操控,但需要状态处理和清理。为每次查询都选择 RPC 只会增加仪式感,而不会改善结果。

持久化工作进程可能泄漏容量。 异步上限在 task_close 之前都会计算活跃的工作进程,即使它们当前的生成已结束。主智能体必须将清理视为完成任务单元的一部分。

两个上限是独立的。 同步运行器和异步运行器各自在自己的代码路径中强制执行三个槽位。这不等于一个全局的六工作进程预算,我也不会将其视为饱和使用两者的许可。

回退行为并非完全相同。 同步运行器会按配置的备选列表处理模型调用失败和进程/结果失败,但任务中止、超时或轮次上限终止会停止尝试循环,而不是触发另一次回退。异步运行器仅在符合条件的提供商或模型失败时重试,并且仅在重放仍然安全时:没有工具调用已开始,且该生成尚未产生可见结果。一旦工作进程已经完成了实际工作,回退可能会导致重复执行,因此运行器会将失败留在产生它的模型上,而不是重新播放提示词。

隔离让薄弱的提示词更快地暴露。 新的工作进程不会继承未说明的决策。更好的工作指令在前期需要更多时间,尽管它们在后期节省时间。

并行化增加了审查工作量。 四个快速的补丁仍然需要集成、综合验证和一个连贯的最终决策。扇出只是移动了工作量,并没有消除它。

扩展层需要测试。 进程控制、JSON 事件解析、RPC 会话、UI 渲染、模型路由和平台相关的终止都是真正的软件。日常使用和崩溃日志能发现问题,但它们不能替代更深层次的自动化覆盖。

15、为什么这个配置适合我

我围绕自己偏好的工作方式构建了这个配置。我想要一个讨论目标、做出决策并看到最终结果的单一场所。我不希望同一个上下文窗口被获取到达那里所需的每一次代码库扫描、文档搜索、实现弯路和审查过程所填满。

专家角色为重复性工作提供了明确的负责人。Scout 收集本地证据。Librarian 处理外部研究。Machinist 或 artisan 负责实现。Oracle 挑战结果。Stevedore 处理运维收尾。我可以为每个角色更改模型、工具和轮次预算,而无需每次重写整个工作流。

两种任务模式覆盖了我实际需要的委派模式。同步任务足以处理有界的查询或审查。当实现需要操控、后续跟进,或在 oracle 发现问题后需要另一轮审查时,持久化 RPC 工作进程更合适。自定义 UI 让我可以监管这两种情况,而不会用工作进程的对话记录淹没主会话。

这并非一个通用的智能体框架,也不是在声称每个编码任务都需要一个团队。我仍然会直接处理一些小的更改。当一项工作变得足够大,以至于调查、实现、审查和发布工作开始竞争同一份上下文时,这个配置才真正有用。

完整的配置已发布在公开的配置仓库。它包含了主提示词、专家定义、扩展、技能、主题和恢复指令。你可以复制整个设置,但更有用的做法可能是取走那些与你工作方式匹配的边界,然后丢弃其余部分。


原文链接:My Custom Pi Configuration for a Multi-Agent Coding Workflow

汇智网翻译整理,转载请标明出处