Pi Coding Agent 不只可以在终端里交互。 它的 RPC 模式把 Agent 作为子进程运行,通过标准输入接收 JSON 命令,再从标准输出持续返回响应和事件。 这条路径适合桌面壳、浏览器控制台、内部工单系统或自动化测试,但它不是把终端输出套进网页那么简单。 真正需要处理的是进程寿命、请求编号、流式事件、会话目录、权限和异常恢复。
RPC 模式解决的不是远程网络调用
这里的 RPC 是本机进程协议。
宿主程序启动 pi --mode rpc,写入一行一个 JSON 对象,并逐行读取 Pi 的标准输出。
它没有自动监听 HTTP 端口,也不会替你做用户认证、TLS 或跨机器访问。
若要提供 Web UI,应由自己的后端持有 Pi 子进程,浏览器只连接这个后端。
不要让公开网页直接生成本机命令或接触工作目录。
先确认命令和版本来自同一套安装
安装 Pi 后,先在计划运行服务的同一个账户下检查:
|
|
Windows 服务、WSL 和普通 PowerShell 可能分别解析到不同的可执行文件。 在 PowerShell 中用下面的命令确认路径:
|
|
升级前记录版本,RPC 客户端应针对一个明确版本测试,而不是默认所有事件字段永远不变。
用最小命令观察协议
在一个空的测试目录启动:
|
|
进程启动后不会出现传统交互界面,而是在等待标准输入里的 JSON 行。 发送请求时必须以换行结束;只写 JSON 但不写换行,解析器可能一直等待。 手工试验适合看首个响应,正式集成必须由程序同时读取 stdout 和 stderr。
把 stdout 当协议通道
stdout 中每一行都应先作为完整 JSON 消息解析。 不要把调试提示、自己的日志前缀或颜色控制符写进同一条管道。 宿主程序的诊断日志写到 stderr 或独立文件。 收到无法解析的行时,记录原文、版本和前后消息编号,但不要把可能含密钥的整行发到公共日志。
一个稳妥的读取循环包含四步:
- 按换行切分字节流。
- 对单行执行 JSON 解析。
- 根据消息类型分发响应或事件。
- 未知类型进入兼容分支,而不是让整个进程崩溃。
请求 ID 是并发关联的核心
客户端可能在上一个任务仍流式输出时发送控制命令。
因此不能用“下一条响应属于上一条请求”这种位置假设。
为每个命令生成唯一 ID,并维护 pending 映射。
最终响应到达后再解除映射;中间事件由会话或当前 turn 状态处理。
|
|
超时只代表宿主没有按时拿到最终响应,不等于 Pi 子进程已经停止。 超时后还应决定发送中止、继续接收,还是终止整个子进程。
Node.js 启动子进程的最小骨架
下面的骨架刻意不绑定具体事件字段,只负责可靠分行与进程退出:
|
|
shell: false 很重要:参数作为数组传入,避免项目路径或用户文本被外壳再次解释。
如果 Windows 找不到 pi,应在启动阶段解析绝对路径,而不是改成拼接命令字符串。
统一封装发送函数
所有写入都经过一个入口,才能限制消息大小、检查进程状态并保证结尾换行。
|
|
大文件不要内嵌为 JSON 字符串。 把文件放入受控工作目录,让 Agent 通过工具读取,并在后端检查真实路径是否仍位于该目录内。
事件流需要显式状态机
一次提示可能经历排队、开始、文本增量、工具调用、工具结果、结束或错误。 前端不要仅维护一个不断追加的字符串,否则工具事件和重试很容易重复显示。 建议为每个 turn 保存以下状态:
queued:请求已写入,尚未确认开始。running:正在接收模型或工具事件。aborting:用户请求中止,等待最终状态。completed:最终响应完整落盘。failed:协议错误、模型错误或进程退出。
事件到达顺序异常时保留原始序号,UI 可以提示“状态不完整”,不要伪装成成功。
先处理背压,再谈流式体验
stdin.write() 返回 false 时表示缓冲区已经承压。
此时暂停继续发送,等待 drain 事件。
浏览器侧也要限制 WebSocket 待发送队列,慢客户端不能无限占用服务器内存。
文本增量可以每 30 至 80 毫秒合并一次再推送,减少 DOM 更新与网络小包。
工具结果通常按事件整体发送,不适合字符级切片。
Provider 与模型由启动参数固定
官方 RPC 文档提供 --provider 和 --model 启动参数。
例如服务可以为不同用途启动不同 worker:
|
|
不要允许普通前端用户提交任意 Provider 名称或模型参数。 后端维护允许列表,并把“快速”“高质量”“本地”映射到审核过的组合。 切换 Provider 前先验证凭据、上下文限制和工具能力;同名模型也可能具有不同输出或计费行为。
密钥只进入子进程环境
API 密钥保存在服务端秘密管理工具或受限环境变量中。 不要把密钥写入 RPC JSON、浏览器 localStorage、会话标题或错误回传。 启动子进程时可以构造最小环境,而不是无条件继承服务进程的全部变量。
|
|
实际变量名取决于所选 Provider;缺失时在启动前报错,避免请求运行到一半才失败。
会话目录决定可恢复性
--no-session 适合一次性任务、CI 和协议测试,进程退出后不依赖历史会话。
需要恢复对话时,使用 Pi 的会话能力,并通过 --session-dir 把数据放到明确位置。
|
|
服务端应把应用用户 ID 映射为内部随机目录名。
禁止用户直接传入 ../、盘符或网络共享路径。
备份会话前确认其中是否包含提示、代码片段、工具输出或商业数据,并设置保留期限。
--name 用来区分受控实例
为长期运行的实例设置可识别名称有助于日志关联:
|
|
名称应由部署配置产生,不要直接使用邮箱、客户名称或工单全文。 日志同时记录应用实例 ID、Pi 进程 PID 和启动版本,才能还原一次异常属于哪个子进程。
Web UI 应隔着自己的后端
推荐链路是:
|
|
后端负责登录、速率限制、工作目录、Provider 白名单和审计。 浏览器只收到展示所需字段,不应看到本机绝对路径、环境变量或未脱敏工具结果。 若 WebSocket 断开,可以让 turn 在后端继续,并允许客户端按事件序号补拉;也可以按产品规则主动中止。
一个用户一个进程并非总是正确
长驻子进程恢复快,但会占用内存并保存更多上下文。 每次请求新建进程隔离更清楚,却增加启动和会话恢复成本。 常见折中是按工作区建立 worker,空闲一段时间后退出,并把并发请求放入每个 worker 的串行队列。 不要让两个请求同时修改同一个 Git 工作区。 需要并行时,为任务创建独立 worktree 或临时副本。
工具权限比模型选择更重要
Pi 子进程继承运行账户能够访问的文件和命令。 部署 Web UI 时,至少执行以下隔离:
- 使用非管理员专用账户。
- 工作目录使用允许列表。
- 禁止访问 SSH 密钥、浏览器资料和生产配置。
- 对外部仓库先做只读或临时副本。
- 对写文件、执行命令和网络访问保留审计事件。
容器可以缩小文件系统范围,但仍需限制挂载、网络和宿主套接字。 把 Docker socket 挂给 Agent 等同于授予很高的宿主控制能力。
中止与关闭必须分开
“停止当前回答”不等于“杀死 Pi 进程”。 优先使用协议提供的中止命令,让当前 turn 收到可识别的结束状态。 只有协议失去响应、stdout 关闭或超过强制终止期限时,才结束子进程。 服务退出时先停止接收新请求,等待短暂宽限期,再关闭 stdin 并回收进程。 Windows 和 Linux 的信号语义不同,必须分别做退出测试。
崩溃恢复不能自动重放写操作
Pi 在工具调用后崩溃时,宿主未必知道文件写入是否已经完成。 不要无条件重放最后一个提示,否则可能重复提交、重复发请求或覆盖文件。 恢复界面应展示最后确认事件,并让用户选择检查工作区、继续对话或创建新会话。 只读查询可以设计幂等重试;写操作需要操作 ID 或执行后验证。
日志分成协议、运行和审计三类
协议日志记录消息类型、请求 ID、事件序号和耗时,不默认保存完整正文。 运行日志记录 PID、版本、退出码、内存和 stderr 摘要。 审计日志记录谁打开了哪个工作区、允许了哪项工具能力以及产生了什么变更。 三类日志分别设置访问权限和保留期。 脱敏规则至少覆盖 API key、Authorization header、邮箱、绝对用户路径和仓库秘密。
先写协议测试替代手工点击
测试客户端可以启动 pi --mode rpc --no-session,发送一条固定请求,并验证:
- 每个 stdout 行都能解析为 JSON。
- 请求 ID 能关联到最终响应。
- 文本和工具事件不会重复结算。
- 超时后 pending promise 会被清理。
- 子进程异常退出时所有等待者收到失败。
再增加包含中文、反斜杠、换行和超长文本的输入,验证 UTF-8 与 JSON 转义。 CI 中不要调用昂贵的真实模型;将子进程替换成按相同协议输出固定事件的假实现。
上线前做四个故障演练
第一,运行中断开浏览器,确认后端不会无限缓存事件。 第二,让 Provider 返回限流或认证失败,确认错误不会暴露密钥。 第三,在工具执行期间终止 Pi,确认系统不会自动重放写操作。 第四,让 stdout 出现未知消息类型,确认客户端记录并继续处理后续兼容消息。 这些结果比“页面能收到第一段文本”更能证明集成可维护。
选择 RPC 还是直接使用库
RPC 的优势是语言无关、进程隔离清楚,也能复用官方 CLI 的启动配置。 代价是要维护子进程、JSON 分行、状态机和版本兼容。 如果宿主本身就是 TypeScript,且需要深入控制 Agent 生命周期,可以评估直接集成对应库接口。 如果宿主是 Python、Go、桌面程序或需要把 Agent 当独立 worker,RPC 通常更容易划清边界。
最小可交付版本应到什么程度
一个可信的首版至少应具备:固定 Pi 版本、单工作区串行队列、Provider 白名单、后端认证、事件序号、超时与中止、stderr 日志、会话目录校验和异常退出清理。 第二阶段再加入多工作区 worker 池、断线续传、工具审批和使用量统计。 不要先做华丽的聊天气泡,再把文件权限与崩溃恢复留到上线后。
结论
Pi Coding Agent RPC 的价值,在于把成熟的 Agent 运行循环放到一个清晰的进程边界后面。
集成质量取决于宿主是否正确管理 JSON 行、请求 ID、事件状态、会话目录和工具权限。
先用 --no-session 完成单请求协议测试,再加入持久会话和 Web UI,最后通过断线、限流与崩溃演练验证恢复策略。
这样得到的不是一个“能聊天的终端转发器”,而是一套可以审计、隔离并持续升级的 Agent 服务。