ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Pi 1.0发布:原生MCP与Durable机制,终端编程代理的可靠进化

Pi 1.0发布:原生MCP与Durable机制,终端编程代理的可靠进化

先交代一下背景。过去大半年,我手里的主力开发环境已经从“编辑器加插件”慢慢变成了“终端加命令行代理”的组合。IDE 里的编程助手体验已经非常成熟,但我一旦要同时操作多个仓库、跑批量修改、让 AI 独立执行一轮“发现问题、改代码、跑测试、提交”的流程,还是更愿意回到终端里。我一直在用的终端编程代理项目 Pi,最近发布了 1.0 正式版。这次更新的重头戏是两块:原生支持 MCP、引入 Pi Durable。我前后用了一周多,把配置、真实任务、断线恢复都试了一遍,这篇就来聊聊 1.0 到底改了什么、实际用起来有没有宣称的那么好、以及有哪些坑需要注意。

如果你是已经在用终端型编程代理的人,或者一直好奇 MCP 和 Durable 这类机制到底怎么落地的,这篇文章应该能帮你省不少折腾时间。

1. 为什么版本号敢从 0.x 直接跳到 1.0

1.0 不是随便挑一个节点发出来的。终端编程代理和 IDE 插件最大的区别在于,它把“对话”和“执行”彻底绑定在一个进程里,还要长期占用你的 shell。这种形态暴露出来的问题,远比我最初想象的更具体。

1.1 旧版本最让人难受的三个场景

第一,工具接入靠“每条管道各写一个适配器”。想让代理查内部文档,要给它写一个搜索接口的调用脚本;想让它操作本地 Git,又得单独接一套命令解析。每接一个新能力,都要在代理内部手动实现一套加载、调用、错误返回逻辑。这种重复劳动在 0.x 时期几乎每周都在发生。

第二,长任务没有任何恢复能力。让 Pi 跑一次跨文件的重构,跑到第几分钟终端不小心关了、SSH 断了、或者超时被系统杀掉,整个任务直接归零。下一次还得把同样的话术重新说一遍,文件检查、修改意图、半成品 diff 全部得重来。这也导致我一直只敢拿它做短平快操作,不敢真正放养一个大任务。

第三,对话记忆和任务状态是两回事。当时的会话记录能吃满上下文窗口,但任务本身没有结构化的“中间状态”。模型说“改完了”,实际改了哪些文件、哪些还没动、哪些内容是需要用户确认后继续执行的,这些信息没有落盘,换个会话立刻失忆。

1.2 1.0 给出的两个核心答案

这次发布实际上是把 0.x 里一堆临时方案收敛成了两个长期机制。

原生 MCP 解决的是工具接入和工具调用规范化的问题。MCP 是模型上下文协议,它把“代理”与“外部工具”之间的关系定义成客户端和服务端。Pi 在 1.0 里直接实现了 MCP Host,不再需要为每个工具单独写适配器,只要对方提供一个符合协议的 server,配置一行就能加载进来。

Pi Durable 解决的是任务状态持久化的问题。它给每个长任务增加了 checkpoint 机制,任务的执行进度、文件补丁、工具调用记录、用户审批记录都会落盘。进程崩了、终端关了、电脑重启了,任务都能从上一个 checkpoint 继续跑,不是从零开始。

1.3 谁最该关注这个版本

我觉得有这么三类人值得立刻升级试试:一是已经在自建工具链、手头有内部服务想暴露给 AI 的开发者;二是经常让代理执行多步骤代码改动的工程师,比如从 issue 描述直接到 commit 的工作流;三是团队里多人共用一台构建机或者远程开发环境、需要随时离线恢复的人。

当然,如果你只是偶尔用 AI 改个 bug,那这个版本对你来说最大的感受可能是“响应变稳了”,长期价值要等你真的放养大任务之后才体现得出来。

2. 原生 MCP 支持:从“能连工具”到“懂工具”

MCP 这个词现在到处都在提,但“支持 MCP”和“原生支持 MCP”差距很大。Pi 1.0 以前也能通过外部脚本间接调用一些 MCP 服务,但那本质上是把协议包在一个可能随时坏掉的桥接层里。1.0 之后,Pi 自己是 MCP Host,这是架构层面的变化。

2.1 原生支持到底改变了什么

MCP 的核心是 JSON-RPC 消息模型,定义了初始化、工具列表、资源列表、工具调用、返回结果这一整套消息流程。原生支持意味着 Pi 主进程负责和服务端做标准握手,工具以统一格式注册进模型可调用的函数列表,工具之间的名称不会冲突,错误码也有统一规范。

用生活类比来说:以前让 Pi 接一个工具,相当于给不同国家的电器各买一个转换插头,麻烦,而且接头标准不一。现在原生支持 MCP 相当于所有电器都换成了同一个国标插座,只要一个插线板就全部搞定。

具体到终端代理看上去的变化有三个。一是加载工具不再依赖插件系统的自定义格式,配置一段 MCP server 描述即可。二是工具调用结果会以标准的tool_result消息返回,模型能看到结构化输出,而不是干巴巴的 stdout 文本。三是工具调用的权限可以在 host 层统一管理,不用每个工具各自实现。

2.2 配置文件长什么样

老用户可能会关心怎么迁移。沿用~/.pi/config.json,1.0 新增了mcpServers字段:

{ "mcpServers": { "local-git": { "command": "pi-fs-git", "args": ["--root", "/srv/myrepo"], "env": { "LOG_LEVEL": "info" }, "transport": "stdio" }, "docs-search": { "url": "https://docs.example.dev/mcp", "transport": "sse" } } }

stdio适合本地起一个子进程,通过标准输入输出通信,启动快、调试直观;sse适合连接远程的 HTTP 服务,比如内部的文档检索服务、代码索引服务。配置好之后,用pi mcp list能查看所有服务是否成功握手。

2.3 工具的组织和权限模型

加载进来的工具会统一命名成mcp__<server名称>__<工具名>,例如mcp__local-git__status。这样即使两个 server 暴露了同名的search,在模型层面也不会混淆。1.0 还把工具暴露拆成了几个权限等级,可以在配置里声明:

{ "mcpServers": { "local-git": { "command": "pi-fs-git", "permissions": { "readOnly": true } }, "deploy-api": { "command": "pi-deploy", "permissions": { "confirmRequired": ["delete", "restart"] } } } }

我对这个设计的评价是:它终于让“AI 能碰写操作”这件事从口头约定变成了强制拦截。只读服务默认连读,写服务默认每步确认,危险操作就算模型主动要调,也会被 host 拦下来问用户。

2.4 一个具体接入案例

我有一次需要让 Pi 结合内部代码索引回答问题。代码索引服务本身暴露了一个 MCP endpoint,返回给定符号的跨仓库引用情况。过去我可能要写一个 scrape 脚本,把结果整理成文本再粘给模型。1.0 里我在配置里加了一行 server 描述,重启之后任务进行中模型就能主动判断什么时候需要查引用、什么时候查定义。

让我印象最深的是,模型调用这种服务时明显更“懂”了。因为 MCP 工具描述里带有参数 schema,模型可以填精确的符号名和范围参数,返回结构也是可解析的 JSON,而不是我手工处理过的格式化文本。区别就像是 API 接口文档和抓包日志的差别,前者天然为消费方设计,后者还要二次解析。

3. Pi Durable:为“跑一半的任务”做好的存档点

如果说原生 MCP 是让 Pi 对外连接的肌肉,那 Pi Durable 就是让任务能扛住意外的骨骼。我对这个功能的兴趣,其实比对 MCP 更大。因为工具接入再花哨,解决不了“任务中途断掉就白干”的根本问题。

3.1 Durable 要解决的痛点

编程代理本质上是一台状态机。它接收任务,拆成子步骤,每步可能调用工具、等确认、拿到结果后再决定下一步。旧版本的问题在于,这个状态机只活在内存里,进程一没就全没了。

终端环境比 IDE 环境更容易出现“突然没”。SSH 会话被网络抖断、笔记本合盖、CI 服务器重启、命令超时被杀,任何一个意外都会让一个跑了大半个小时的改动任务直接失效。Durable 的定位就是给这台状态机加外置的存档系统。

3.2 任务状态机与 checkpoint 机制

Pi 1.0 的 Durable 会把任务的执行抽象成这几个阶段:pending、running、waiting_tools、waiting_confirm、completed、failed。每次状态迁移之前,它都会写一个 checkpoint:

  • 当前任务目标和尚未完成的子任务队列
  • 已经执行过的工具调用及其结果摘要
  • 工作区内已应用的文件补丁和 diff
  • 用户已经给过的确认记录
  • 对话上下文压缩后的 summary,用于恢复后的记忆重建

这些数据会写进~/.pi/durable/<任务ID>/,元信息在 SQLite 里,大文件以快照形式存盘。你可以把它类比成游戏存档。但和普通存档不同的是,它还要保存“接下来该干嘛”的队列,这样恢复之后不会只记得做了啥、忘了还剩啥。

3.3 恢复流程是怎么串起来的

恢复时 Pi 会做三件事。先校验工作区状态,计算当前目录文件哈希,和 checkpoint 时记录的快照哈希对比,不一致就要先处理冲突,不允许“稀里糊涂接着改”。然后加载 summary,让模型快速重建对任务的认知。最后从最近的 checkpoint 继续执行待办子任务,而不是回退到最早的对话重新推导。

这一步很关键。和“上下文恢复”不同,Durable 不是把历史消息全部塞回来,而是用摘要加结构化队列重建上下文。前者是重放录像,后者是直接载入存档。录像的问题在于重放到一半模型还是会糊涂,存档则精确定位到任务进度。

3.4 Durable 对工作流带来的变化

实践下来,最大的变化是我终于敢让 Pi 干“几个小时尺度”的活了。

以前跑一个涉及两三百个文件的大规模代码风格统一,我得坐在屏幕前盯日志。现在我可以直接放养,中途去开会、合上盖子,回来用一条命令恢复,它会告诉我上次停在哪、还需要多久、有没有出现需要人工决策的点。这种确定感,是普通会话保存给不了的。

4. 完整实测:安装、配置、跑通一次跨文件重构与断线恢复

光看文档不够,这一节是我实际跑通的全过程。环境是普通的 Linux 开发机,仓库是一个中型 Python 项目。我会把关键命令和输出形态写清楚。

4.1 安装与初始化

我是直接用官方安装脚本装的,另外它也支持 cargo 安装。两种方式任选:

# 方式一:官方安装脚本 curl -sSL https://get.pi.example.dev | bash # 方式二:cargo 安装 cargo install pi-cli

这里有个提醒:从网络跑安装脚本之前,最好先下载下来看一眼内容,确认没有奇怪行为。安装完之后运行pi doctor检查环境,能帮你发现缺省配置。

pi doctor

它会检查默认模型配置、工作目录权限、MCP 配置是否合法。首次使用建议执行pi init,按提示选择默认模型、设置任务存储目录。

4.2 配置一个本地只读 MCP 服务

我想让 Pi 能直接读取仓库里的文件信息,所以先配了一个只读的本地文件系统服务。配置文件直接改~/.pi/config.json:

{ "mcpServers": { "repo-reader": { "command": "pi-fs", "args": ["--root", "/home/me/work/example-project"], "transport": "stdio", "permissions": { "readOnly": true } } } }

然后执行:

pi mcp list

正常情况下会看到repo-reader状态为connected,后面跟一串可用的工具名,比如mcp__repo-reader__read_file、mcp__repo-reader__list_dir。这个阶段有问题先别急着跑任务,把连接问题清掉再继续。

4.3 实操:一个跨文件日志规范化任务

我给 Pi 下的任务是:检查src/utils/logger.py的当前日志格式,把整个src目录下调用logger.info的地方统一改成带模块前缀的新格式,并且不能改动测试文件的预期输出。

命令:

pi "将仓库 src 目录下所有 logger.info 调用改为带模块前缀的形式,模块前缀从文件路径推导。先检查 src/utils/logger.py 了解现有格式,改完后给我一份改动文件清单。"

这个任务之所以危险,是因为它涉及全局改动,而且有“不能改测试预期输出”的约束。Pi 的处理过程大致分成几个阶段。先调用mcp__repo-reader__read_file读取 logger 实现,再列出所有可能受影响的文件,用 git diff 给出一份改动预览,然后逐个应用修改,最后回到终端让我确认是否提交。

我注意到一个细节:因为 MCP 工具注册进来了,模型能够自主决定“先读配置、再搜调用点、再生成 patch”。整个过程里工具调用是有序的,而不是一股脑把文件内容塞进上下文。这也降低了上下文被快速占满的风险。

4.4 Pi Durable 挂起与恢复的真实场景

同一段任务,我故意做了一个测试。在任务执行到大约修改到第 40 个文件的时候,直接关掉终端模拟断线。重新打开终端后:

pi durable list

输出显示一个任务 ID 处于running转interrupted的状态。恢复命令:

pi durable resume 42

它没有重新解析我原来的那句话,而是告诉我“已从 checkpoint 恢复,还有 15 个文件未处理”,并继续输出新的改动。整个恢复过程用了大概 20 秒,其中大部分时间花在重建摘要和校验工作区哈希上。对比如果是旧版本,我大概率要重新组织一遍任务描述、重新确认改过的文件,然后再等它从头把所有文件重读一遍。

我特意验证了一下恢复后的结果:最终 diff 和“一口气跑到底”的版本基本一致,没有因为中断多改或者漏改。这说明 checkpoint 保存的文件补丁和任务队列是可靠的。

5. 实测中踩到的坑与避坑经验

1.0 总体是稳的,但新机制也带来了一批新问题。这一节记录我实际踩过的坑,每条都能帮你在自己的环境里少花冤枉时间。

5.1 MCP 服务进程卡住或假死

我在接一个内部 SSE 服务时遇到过工具调用一直“pending”的情况。表面看 Pi 没有报错,但等了很久都没有工具结果返回。排查下来是服务端在长轮询场景下没有实现超时,连接挂住不主动断开。

建议在配置里给 MCP 调用加上超时约束:

{ "mcpServers": { "docs-search": { "url": "https://docs.example.dev/mcp", "transport": "sse", "toolTimeoutMs": 15000 } } }

如果工具是本地 startio 进程,优先确认服务本身是不是每行输出都有 flush。很多子进程因为输出缓冲,导致 Pi 等不到完整 JSON-RPC 消息。

5.2 Durable 恢复时的工作区不一致

有一次中断后,我记不清当时改到一半的文件是否被我手动动过,直接跑pi durable resume,结果被拒绝:工作区哈希与 checkpoint 不一致。最初我以为是 bug,后来想明白了,这是有意设计。checkpoint 里记录的文件状态只对你“没有改过文件”的情况有效。一旦工作区变了,继续在这个基础之上应用后续 patch,很可能把不一致扩散。

正确的做法是:如果你改过文件,先git stash把改动收起来,让工作区回到 checkpoint 对应的状态,再恢复任务。如果不想保留手动改动,可以直接用pi durable discard 42 1_old丢弃 checkpoint,重新从当前状态发起新任务。千万别让 Pi“无视差异继续跑”,后续 diff 一定会错位。

5.3 权限模型造成的“假失败”

这是个非常容易误判的坑。我给某个 MCP 服务配了写权限,但工具列表里某些操作在 server 端被标记为需要确认,Pi 这边又没声明confirmRequired,执行阶段就会出现模型“调用被静默拒绝”。表面上看是工具调用失败,实际是 host 层的权限策略拦掉了。

排查命令是看任务日志里的permission_denied字段。确认之后,要么放宽 server 声明,要么在 Pi 配置里显式把风险操作放进confirmRequired。我更推荐后者,宁可每次多一次确认,也不要在生产仓库里被代理偷偷删了一个分支。

5.4 模型上下文压缩与 Durable 的配合度

Pi Durable 的恢复依赖上下文 summary。如果 summary 压缩过度,模型恢复后可能忘了任务里的隐性约束,比如“不能改动测试预期输出”这种话,一旦被挤出摘要,后续步骤就可能跑偏。

我的经验是,重要约束不要只靠对话上下文,尽量写进任务描述本身,或者直接用别名模板固化。例如:

pi alias save refactor-log "统一所有 logger 调用格式,需保留测试预期输出,完成后生成 diff 预览"

之后每次都用pi alias run refactor-log,这样即便中途要恢复,约束也会在任务重启时重新注入,不依赖摘要是否完整。

6. 适用场景、边界与我对 1.0 后续的想法

这一版功能很明确,但也不是万金油。结合我的体验,说说它最适合干什么,以及哪些地方暂时不建议硬上。

6.1 最值得用 Durable + MCP 的场景

首先是批量代码改动。会有明显收益。当任务规模超过一百个文件,或者需要跨仓库读取信息再回写,Durable 的价值几乎是立刻显现的。其次是夜间跑任务。可以让 Pi 在无人值守的情况下修改、测试、生成报告,第二天验收结果就行。第三是团队内部的“半自动运维开发”,把内部 API、文档、代码索引全部用 MCP server 暴露给代理,让它在终端里自由调度。

6.2 暂时不建议的场景

如果是高频交互式结对编程,你更习惯每改一行都盯着编辑器里的内联 diff,那 Pi 这种终端代理形态可能仍然不适合你。Durable 的恢复机制对“你已经确认过、但还没执行的子任务”支持不错,但如果你是在和模型激烈讨论方案的阶段,那种开放式的探索过程本身不适合做 checkpoint,更适合用普通对话记录。

另外,团队共享同一份 durable 任务目录时要注意冲突,它目前还没有做多人并发锁。建议一个任务只由一个终端进程负责。

6.3 后续值得关注的方向

我挺期待后续两个能力。一是 durable 任务的多人可见性,允许团队看到“某个任务跑到哪了、卡在哪一步”,这对远程协作很有用。二是 MCP 权限分组的粒度细化,现在只能声明只读或写,但如果能按目录、按分支、按命令条件做更细的策略,生产环境的安全性还会上一个台阶。

回到这一周多的使用感受,我最喜欢的变化其实是心理上的。以前让代理做大任务,总要留一份戒心,怕它中途断了我还得收拾烂摊子。现在 Ctrl+C 断掉也没那么慌,因为 Durable 已经把最脏的中间状态接住了。最后分享一个团队里的小约定:我们执行长任务时会在进度里带上任务 ID,虚机重启后直接pi durable resume <id>接着跑,不用每个人重新解释自己在干嘛。这种确定性,就是 1.0 最大的价值。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进