10 min read教程

PiAgent 14|生产化清单:可恢复、可观测、可约束

从“代码能跑”走向“服务可长期运行”,为持久化、安全、成本、取消和回归测试建立边界

PiAgent 14|生产化清单:可恢复、可观测、可约束#

从“代码能跑”走向“服务可长期运行”,为持久化、安全、成本、取消和回归测试建立边界

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

Pi 的好处是这些问题没有被藏在不可替换的服务里;代价是宿主必须自己把边界拼完整。可以用四条线检查系统:

text
用户输入


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

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

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

1. 身份与会话隔离#

生产服务首先要建立关系:

text
用户身份 → 租户 → 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. 错误、重试与幂等#

错误处理要分层:

text
Provider 暂时错误 → 有上限的重试
工具参数错误     → 回写模型,允许修正
权限拒绝         → 立即停止该动作
业务无结果       → 有效结果,不当作异常
用户取消         → 不重试,释放资源
持久化失败       → 不宣称成功,进入恢复流程

工具若可能产生外部副作用,必须考虑重试导致的重复执行。给操作分配幂等键,或先查询状态再执行;不能因为网络超时就默认“没有执行成功”。

6. 上下文与压缩质量#

上线前要明确:

  • 模型窗口与输出预留是多少。
  • 单个工具结果最大多少字节和行数。
  • 何时自动压缩,何时允许用户手动压缩。
  • 压缩摘要是否保留目标、文件、测试、决策和风险。
  • 切点是否尊重 tool call/result 配对和当前分支。
  • 压缩失败时如何保留原状态。

最有效的质量指标不是“摘要字数”,而是压缩后 Agent 能否继续完成同一任务。可以设计恢复场景:压缩前修改文件、执行测试、插入分支,再让新运行回答当前状态是否一致。

7. Web 接入与连接生命周期#

如果通过 SSE 输出事件,至少处理:

  • 请求身份和 Session 归属。
  • 请求体大小、并发上限和速率限制。
  • 客户端断开时调用 session.abort()
  • prompt 抛错时发送脱敏的 error 事件。
  • done 只在真正收束后发送。
  • 连接关闭后取消订阅,避免内存泄漏。

一个单接口的公共事件协议可以只暴露 texttool_starttool_endqueuedoneerror。内部事件、工具参数和模型响应不应直接成为公共 API。

8. 版本和配置漂移#

Pi 的包、扩展 API、模型 Provider 和项目资源都会变化。生产服务要记录:

  • Pi 包版本与 lockfile。
  • 系统提示词、工具 Schema 和扩展版本。
  • 模型 Provider 与模型 ID。
  • 会话格式版本和迁移策略。
  • 运行时配置的来源与更新时间。

升级时重点回归 AgentSession 生命周期、事件字段、工具调用协议、压缩切点、RPC JSONL 和 dispose/abort 行为。不要只看 TypeScript 编译通过;协议变化往往会在运行时才暴露。

9. 回归测试应该测行为#

建议用假的模型流和假的工具后端覆盖这些场景:

text
纯文本回答 → 只产生 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/

相关文章