10 min read教程

M14. 生产化清单:可恢复、可观测、可约束

用可恢复、可观测、可约束的四条线检查 Agent 生产化边界。

M14. 生产化清单:可恢复、可观测、可约束#

一个 Agent 能够完成任务,只能说明模型和工具连接成功。要进入长期运行的应用,还需要回答四个问题:它做过什么?失败后能否恢复?危险动作由谁批准?上下文变长后会不会悄悄退化?

Pi 的好处是这些问题没有被藏在一层不可替换的服务里。坏处是你必须自己把边界拼完整。本章给出一套最小的生产化检查框架。

1. 用四条线组织系统#

text
用户输入


AgentSession ── subscribe ──▶ 事件投影 / UI / 指标

   ├── Agent Loop ──▶ 模型 + 工具
   │       │
   │       └── 扩展策略:校验、阻断、命令、动态能力

   └── SessionManager ──▶ JSONL 会话树 / 压缩 / 分支

四条线分别解决:运行、控制、观察和恢复。它们可以在一个进程里实现,但不应该共享所有状态。比如事件投影可以丢失并重建,Session Tree 则要保证 entry 的父子关系不被随意改写。

2. 工具层:把副作用变成契约#

生产工具至少要有以下字段和行为:

  • 输入 schema:拒绝缺字段、非法枚举和超长内容。
  • 明确 description:让模型知道何时应该调用,不要把权限政策藏在描述里。
  • 可取消执行:网络、文件和子进程都要接收 AbortSignal
  • 稳定结果:给模型返回可行动的文本,给程序返回结构化 details
  • 幂等键:重复消息或重试不会重复扣费、发信或写入外部系统。
  • 审计信息:记录 tool name、参数摘要、结果状态和操作者,不要记录密钥。

扩展的 tool_call 钩子可以做一次策略阻断,但不能替代 OS 权限和服务端授权。模型提示词、工具描述和用户确认窗口都不是最终安全边界。

3. 会话层:append-only 不等于永不整理#

Pi 的 session file 使用 JSONL:首行是 session header,其余每行是带 idparentId 和时间戳的 entry。消息、压缩、分支摘要、标签、模型变化等都可以成为不同类型的 entry。

这个结构提供两个重要性质:

  1. 追加新节点不会修改旧节点,回退只移动当前 leaf。
  2. 从旧 entry 继续工作会产生新分支,原始尝试仍然可追踪。

但文件会增长,消息历史也会超过模型窗口,所以恢复系统还需要 compaction。压缩不是删除历史,而是追加一条带摘要、保留起点和 token 统计的压缩 entry,然后在重建上下文时按路径选择可见内容。

ts
import { SessionManager } from "@earendil-works/pi-coding-agent";

const session = SessionManager.open("/path/to/session.jsonl");
const tree = session.getTree();
const currentPath = session.getPath();
const leaf = session.getLeafEntry();

if (leaf) {
  console.log({
    leaf: leaf.id,
    pathLength: currentPath.length,
    childCount: session.getChildren(leaf.id).length,
    treeSnapshotBytes: JSON.stringify(tree).length,
  });
}

上面的读取用于诊断和导航。不要通过手写 JSONL 来改变 leaf;让 SessionManager 的 branch、label 和 session 操作维护不变量。

4. 恢复流程要区分三类状态#

一次运行至少有三类状态:

状态例子恢复方式
已提交事实完成的 user/assistant/toolResult 消息从 session file 重建
运行中状态当前流、活动工具、取消信号结束旧运行,按策略重启
外部副作用已发出的请求、写入的文件、扣费依赖幂等键和外部系统查询

不要把“进程崩溃前最后一个 delta”当作已提交事实;也不要重放所有工具调用来猜测外部系统状态。服务启动时,应先打开 session,检查最后一个完整 entry,再根据工具幂等记录决定是否继续。

5. 观测层:既看结果,也看过程#

只记录最终答案无法定位 Agent 为什么失败。至少应保留以下四类指标:

  • 生命周期:一次 prompt 的开始、结束、取消、重试和耗时。
  • 工具:每个工具的调用次数、成功率、耗时、错误类型和输出大小。
  • 上下文:压缩次数、压缩前 token 估算、保留路径长度和摘要失败次数。
  • 成本与质量:模型、thinking level、token 使用、用户重试率和验收通过率。

原始输入可能包含源代码、隐私或密钥。事件上报前应做字段级脱敏,尤其是工具参数和工具输出。不要因为 Pi 的事件很透明,就把完整上下文无条件复制到第三方日志服务。

6. 扩展与版本管理#

扩展是运行时代码,不是静态 prompt。上线时要像管理插件一样管理它:

  1. 锁定 Pi 包版本和扩展版本。
  2. 启动时记录扩展清单与校验和。
  3. 让动态工具启用有明确的租期或撤销操作。
  4. tool_callsession_startmessage_end 等关键钩子写回归测试。
  5. 升级 Pi 时检查事件联合类型、session entry schema 和 compaction 行为。

尤其不要只测试“扩展能加载”。加载成功不代表工具描述正确、阻断分支可达或热重载后不会留下旧订阅。

7. 上线前失败演练#

一套有效的验收不应该只执行成功案例。建议至少演练:

故障预期结果
模型请求超时运行可取消,session 不写入半截 assistant 消息
工具参数非法调用被 schema 拒绝,模型收到可纠正的错误
工具执行异常结果标记为错误,Agent 有机会决定重试或停止
用户拒绝危险动作工具不执行,拒绝原因可追踪
压缩调用失败不覆盖旧历史,发出明确的 compaction 错误
进程在工具后崩溃重启后能识别最后完整 entry,不盲目重复副作用
事件消费者断开Agent 与展示层解耦,重新连接可从 session 恢复
扩展版本不匹配启动失败或降级,而不是静默使用错误工具

8. 最小上线清单#

  • 每个工具有 schema、超时、取消、错误和幂等策略。
  • 危险工具有宿主权限检查;容器或 OS 边界已配置。
  • session file 的目录、权限、备份和清理策略已确定。
  • compaction 和 branch summarization 有失败测试。
  • 事件投影与持久化消息分开,敏感字段已脱敏。
  • prompt、tool result、扩展日志和业务数据库都有大小上限。
  • 可以从 session id 定位一次运行的模型、工具、版本和错误。
  • Pi 升级有 schema 检查和一组 golden session 回放。

9. 结语#

Pi 的生产化不是给一个“大 Agent”再套一层服务,而是把运行循环、能力扩展、事件投影和会话恢复分别做成可验证的边界。这样,当模型换了、工具加了、上下文压缩策略变了,系统仍然知道哪些事实可以恢复、哪些副作用不能重放、哪些状态必须由人决定。

资料

本文依据 Pi SDK 文档SessionManager 源码Compaction 文档Pi Extensions 文档重写。上线前请再次核对当前版本的接口和 entry schema。

相关文章