统一 Agent 操作生命周期
OpenProgram 使用同一套流程处理权限判断、工具执行、失败重试、结果保存和中断恢复。保留现有 Agent 循环、权限服务、执行数据库、持久化审批等待和前端状态投影,不增加第二套工作流引擎。
完整技术契约以 英文设计为准。本页说明设计决策与实施边界。
当前实现与剩余问题
- 近期失败表与检查点统一限制为 16 条,新增失败不会使检查点容量溢出。
- Bash 重复判断排除只用于显示的 description,实际执行参数与其他工具字段仍参与判断。
- auto 模式在分析命令前按工具名称拒绝 Bash;分类器只读取参数前 800 个字符,缺少可信工作目录和任务上下文。因此不能直接删除拒绝名单。
- 审批等待在调用前同步准备,自动分类却发生在执行阶段。分类拒绝后直接弹审批会缺少可恢复的持久化等待记录。
- 最后一次拒绝虽然有日志,但检查点异常使执行记录仍保留 dispatched,导致错误地进入操作结果待核对状态。
采用哪些已有策略
| 参考产品 | 公开依据 | 采用方式 |
|---|---|---|
| Codex | 工具调度源码先审批,再选择沙箱并执行;拒绝与沙箱失败分别处理。 | 所有工具共用调用前预检;申请额外权限必须符合策略,重试本身不授予权限。 |
| Claude Code | 自动审批文档规定连续 3 次或累计 20 次分类拒绝后,交互会话恢复人工审批。 | 采用独立的连续和累计计数,复用持久化审批。非交互任务保留拒绝并继续其他获准工作。 |
| Claude Code | 权限规则区分整个工具禁用和具体操作禁止。 | 工具可见性与实际能力一致;已生成的旧调用仍须经过最新权限检查。 |
| Claude Code | 文件检查点不撤销 Bash 或远程系统操作。 | 区分会话恢复、文件撤销与执行结果核对,不宣称任意外部操作恰好执行一次。 |
Codex 源码链接固定到具体版本;产品文档可变,实现前保存对应证据。Claude Code 内部失败表和结果存储机制未由这些公开资料证实。以下结果持久化方案属于 OpenProgram 自身设计。
三个独立状态
| 状态 | 取值 | 回答的问题 |
|---|---|---|
| 权限 | allow / ask / deny | 当前身份与规则是否允许这次操作开始? |
| 执行结果 | not_started / succeeded / failed / unknown | 操作是否执行,结果是否确定?失败可能已有部分副作用。 |
| 结果交付 | pending / recorded / projected | 结果是否保存,是否同步到会话和界面? |
准备 → 权限判断 → 必要的持久化审批 → 再次校验 → 执行 → 保存结果 → 检查点 → 更新界面
↘ 拒绝 → 保存“未执行” → 模型继续其他工作或说明阻碍
每次操作通过 execution ID、tool call ID、参数摘要、权限版本绑定。权限拒绝表示这次操作未开始,不表示整个任务完成。只有操作可能执行、结果无法确认时,才使用 reconciliation_required。
调用前预检
- 保留当前硬约束、身份能力、计划模式、显式 deny、ask 和精确授权的优先级,分类器不能覆盖它们。
- 在登记执行前异步审查完整操作。传入可信工作目录、权限范围和必要的用户要求;模型描述与仓库文本视为不可信输入。过大参数不得截断后批准。
- 分类器超时、服务失败、取消和明确风险拒绝分别记录。没有明确允许就不执行,也不能把“无判断结果”统计成风险结论。
- 仅对具备相应能力的交互式所有者创建持久化审批。硬约束和明确禁止不能变成可覆盖的审批。
- 执行前重新核对权限版本、参数摘要、审批身份和文件前置条件。审查期间规则改变,旧结论失效。
不要在所有拒绝结果上统一写 execution_started=false:调用前拒绝可以确定未执行,但沙箱执行后申请额外权限被拒,原操作可能已经部分执行。
失败与继续执行
- 参数错误:返回结构化错误,允许修正实际参数。
- 权限拒绝:说明规则和有效解决方式;未改变权限或实际条件,不因修改描述重复尝试。
- 临时故障:只有确定未执行或具备可靠幂等支持时,才进行有次数限制的自动重试。
- 确定失败:返回完整必要输出供模型判断;其他获准且有意义的工作继续。
- 结果未知:保留操作证据,进入核对流程,不能直接重试。
- 运行时故障:单独报告,不能把已知工具结果重新变成未知。
近期失败表使用固定容量;完整历史保留在事件记录中。存储淘汰不重置累计拒绝计数,也不改变权限。Bash 只排除明确属于显示用途的 description,保留 command、工作目录和权限等执行字段;不能对用户工具任意删除同名字段,不能猜测任意 Shell 脚本是否等价。
交互式 auto 回退采用连续 3 次、累计 20 次分类拒绝的默认值。获准操作只清除连续计数;累计计数按定义的会话周期保存。恢复和并发调用通过操作身份避免重复计数。回退至人工审批不解除任何显式禁止。
结果保存与恢复
复用现有数据库,将权威结果记录与可选诊断、显示状态分开。结果记录绑定调用身份、结果摘要、是否开始执行和错误原因,检查点构建失败不能丢掉已知结果。
| 恢复时的情况 | 处理 |
|---|---|
| 结果已保存,检查点未更新 | 使用结果补齐继续执行状态,不再次调用工具。 |
| 检查点已保存,界面未更新 | 只重放界面投影。 |
| 有执行记录,没有可信结果 | 按原有副作用核对规则处理。 |
外部系统操作不能与本地 SQLite 普遍组成一个事务,因此不承诺所有副作用只执行一次。修复旧会话必须核对精确调用与可信权限事件,不能只根据一段任意工具文字猜测未执行,更不能把整个任务标成完成。
文件职责与实施顺序
| 顺序 | 模块 | 验收要求 |
|---|---|---|
| 1 | Agent failure_policy、循环与 continuation | 超过 16 条不同失败仍可保存;改描述不重置判断;旧合法检查点可读。 |
| 2 | agent/permissions | 完整命令预检;危险尾部不会被忽略;审查期间取消或撤销权限不执行。 |
| 3 | 现有权限状态与审批等待 | 拒绝回退有持久化等待;非交互不创建无法回答的审批;恢复不重复计数。 |
| 4 | production_driver 与 execution | 结果保存后故障,重启不重复执行写操作;真正未知的结果仍要求核对。 |
| 5 | 工具可见性与 Web 状态投影 | 规则撤销立即生效;界面与后端一致显示原因和有效恢复动作。 |
Terminal、Web、用户应用和普通工具通过现有公共入口使用同一生命周期,不建立各自的权限与重试框架。每阶段独立复现、实现、规格审查、质量审查及完整验证。紧急容量修复与权限放宽分开交付。
实现状态
权限预检和持久化拒绝回退已通过之前的独立审查与完整 Python 检查。结果恢复、宿主执行证据、工具可见性与结果显示正在进行集成验证,尚未部署或合并。
拒绝计数按会话和用户权限版本隔离。显式权限版本变化开始新的计数周期。允许的操作及经审批的回退操作只重置连续次数,累计次数在累计阈值触发的审批完成或周期变化时重置。现有 SQLite 事务内的执行事件保存准确调用身份与判断,恢复或并发调用不会重复计数。没有布尔判断的分类器故障不改变计数。回退使用已有用户审批等待,不能覆盖显式禁止。
审批接入
复用现有持久化审批等待。自动风险拒绝不占执行失败重复次数,使相同操作能够达到人工审批阈值。无有效判断的检查错误不自动授权。仅宿主在已授权实现开始前记录执行;已完成和未执行结果均先于检查点保存,不信任应用返回的执行状态。每次模型请求按适用规则过滤工具,保留已保存执行契约。实时事件和历史记录传递宿主结果状态。累计计数在累计阈值触发的审批完成或权限版本变化时重置。
Claude Code 文档提供拒绝回退策略,Codex 调度实现提供先审批后执行和受策略限制的升级。二者不构成适用于本项目数据库的通用恢复算法;结果保存使用现有事务和检查点机制。
模型请求事件
每次发出的模型请求具有唯一 request_id,并且恰好记录一次开始和结束。结束状态区分完成、失败、取消和未完整结束,覆盖传输异常及缺少终止事件的 EOF。异常与取消保持原样传播;事件记录不重放工具,也不增加模型重试。原有部分文本返回仍标为未完整结束。完成观察器不把失败请求视为成功回答。