PiAgent 14|生产化清单:可恢复、可观测、可约束#
从“代码能跑”走向“服务可长期运行”,为持久化、安全、成本、取消和回归测试建立边界
一个 Agent 能够完成一次任务,只能说明模型和工具连接成功。进入长期运行的应用,还要回答:它做过什么?失败后能否恢复?危险动作由谁批准?上下文变长后会不会退化?多个用户会不会串线?
Pi 的好处是这些问题没有被藏在不可替换的服务里;代价是宿主必须自己把边界拼完整。可以用四条线检查系统:
用户输入
│
▼
AgentSession ── subscribe ──▶ 事件投影 / UI / 指标
│
├── Agent Loop ──▶ 模型 + 工具
│ │
│ └── 扩展策略:校验、审批、阻断、动态能力
│
└── SessionManager ──▶ JSONL 会话树 / 压缩 / 分支1. 身份与会话隔离#
生产服务首先要建立关系:
用户身份 → 租户 → Session ID → Agent 运行 → 工具权限不要让客户端直接决定可以访问哪个 session 文件,也不要用一个全局 AgentSession 服务所有用户。每次加载会话时都要校验所有权;每次工具执行时都要再次校验资源权限。历史记录证明过去发生过什么,但不会自动授予当前权限。
2. 工具层的安全边界#
生产工具至少应具备:
- 输入 Schema:类型、枚举、长度和结构限制。
- 业务校验:用户能否访问目标资源,操作是否在额度内。
- 最小权限:只提供当前任务所需的文件、网络和系统能力。
- 超时与取消:所有外部调用接收
AbortSignal。 - 审批或 dry-run:写入、部署、删除、发送等动作先展示影响范围。
- 审计记录:保存调用 ID、工具名、参数摘要、结果类别和操作者。
模型可以提出危险动作,但不能成为最终授权者。工具定义、扩展钩子和基础设施权限必须形成纵深防御。
3. 运行状态与持久化#
区分三类状态:
| 状态 | 例子 | 持久化要求 |
|---|---|---|
| 运行态 | 当前 stream、队列、abort signal | 通常不直接落盘 |
| 对话态 | user、assistant、tool result | 可恢复地保存 |
| 业务态 | 订单、审批、任务状态 | 由业务数据库负责 |
不要把业务状态只放在 prompt 或摘要里;摘要可以丢失细节,也不能替代事务。Agent 生成“已发送邮件”的文本不等于邮件系统已经提交成功,真正事实应来自外部系统的确认。
4. 事件与可观测性#
每次运行至少要关联一个 trace/run ID,并记录:
- 用户、租户和 Session 的非敏感标识。
- Provider、模型 ID、thinking level 和配置版本。
- 每个 turn 的开始、结束和 stop reason。
- 工具调用 ID、工具名、耗时、错误码和取消状态。
- 重试次数、压缩前后 token 估算。
- 最终状态:成功、失败、取消、超时或权限拒绝。
文本增量不一定需要逐片写数据库,可以合并或采样;工具开始/结束、重试、压缩和 settled 事件则应优先保留。日志必须脱敏,尤其是 API Key、Cookie、完整 SQL、私有路径和用户内容。
5. 错误、重试与幂等#
错误处理要分层:
Provider 暂时错误 → 有上限的重试
工具参数错误 → 回写模型,允许修正
权限拒绝 → 立即停止该动作
业务无结果 → 有效结果,不当作异常
用户取消 → 不重试,释放资源
持久化失败 → 不宣称成功,进入恢复流程工具若可能产生外部副作用,必须考虑重试导致的重复执行。给操作分配幂等键,或先查询状态再执行;不能因为网络超时就默认“没有执行成功”。
6. 上下文与压缩质量#
上线前要明确:
- 模型窗口与输出预留是多少。
- 单个工具结果最大多少字节和行数。
- 何时自动压缩,何时允许用户手动压缩。
- 压缩摘要是否保留目标、文件、测试、决策和风险。
- 切点是否尊重 tool call/result 配对和当前分支。
- 压缩失败时如何保留原状态。
最有效的质量指标不是“摘要字数”,而是压缩后 Agent 能否继续完成同一任务。可以设计恢复场景:压缩前修改文件、执行测试、插入分支,再让新运行回答当前状态是否一致。
7. Web 接入与连接生命周期#
如果通过 SSE 输出事件,至少处理:
- 请求身份和 Session 归属。
- 请求体大小、并发上限和速率限制。
- 客户端断开时调用
session.abort()。 - prompt 抛错时发送脱敏的
error事件。 done只在真正收束后发送。- 连接关闭后取消订阅,避免内存泄漏。
一个单接口的公共事件协议可以只暴露 text、tool_start、tool_end、queue、done 和 error。内部事件、工具参数和模型响应不应直接成为公共 API。
8. 版本和配置漂移#
Pi 的包、扩展 API、模型 Provider 和项目资源都会变化。生产服务要记录:
- Pi 包版本与 lockfile。
- 系统提示词、工具 Schema 和扩展版本。
- 模型 Provider 与模型 ID。
- 会话格式版本和迁移策略。
- 运行时配置的来源与更新时间。
升级时重点回归 AgentSession 生命周期、事件字段、工具调用协议、压缩切点、RPC JSONL 和 dispose/abort 行为。不要只看 TypeScript 编译通过;协议变化往往会在运行时才暴露。
9. 回归测试应该测行为#
建议用假的模型流和假的工具后端覆盖这些场景:
纯文本回答 → 只产生 text 事件并正常 settled
工具调用 → start/end 配对,结果回到下一轮上下文
工具失败 → isError 可见,模型能收到稳定错误
自动重试 → agent_end 次数与 settled 语义正确
用户取消 → 模型流和工具都停止,不继续重试
压缩 → 切点合法,摘要保留关键事实
分支 → 当前 leaf 正确,旧路径仍可恢复
并发 → 不同 Session 隔离,同一 Session 按策略排队或拒绝这些测试比快照最终回答更有价值,因为它们验证的是运行时协议,而不是某次模型措辞。
10. 上线前的最小清单#
- 每个用户都有隔离的 Session 和工具权限。
- 高风险工具有审批、dry-run 或最小权限限制。
- 工具参数、结果和日志都有大小与敏感信息边界。
- abort 能传播到模型流、工具和子进程。
- 重试有次数、超时和幂等策略。
- compaction、分支和恢复有行为测试。
- 事件投影有稳定版本,不直接暴露内部协议。
- 运行、工具、模型、压缩和持久化都有可观测字段。
- 升级有锁定版本、迁移方案和回归测试。
小结
生产化不是给 Agent 再套一层 HTTP,而是把四条线补完整:运行循环要可停止,工具副作用要可约束,事件过程要可观测,会话历史要可恢复。Pi 把这些能力暴露出来,也把最终责任留给宿主。能明确回答“谁能做什么、做过什么、失败后怎么办、重启后如何继续”,才算真正接近生产级 Agent。
源码定位
- Session 与运行控制:
packages/coding-agent/src/agent-session.ts - 工具执行:
packages/agent/src/agent-loop.ts - 会话树:
packages/coding-agent/src/core/session-manager.ts - 扩展策略:
packages/coding-agent/src/extensions/ - RPC 与无头接入:
packages/coding-agent/src/modes/rpc/