文章目录
- Agent 为什么会失败?如何定位问题?——从 【OpsArk 运维智能体】的执行链说起
- 1. 先把 Agent 拆成一条可以检查的执行链
- 2. 历史案例:工具提案为什么在准备后被拒绝?
- 3. 回归场景:端口检查成功,是否就可以继续部署?
- 4. 长任务停止与执行结果未知,要分开诊断
- 没有新输出时,先检查进展证据
- 连接中断后,先确定动作有没有生效
- 5. 实际定位时,沿着这五步走
- 第一步:固定用户目标与当前版本
- 第二步:串起同一次运行的身份
- 第三步:找到最早违背约束的变化
- 第四步:根据失败层选择恢复动作
- 第五步:把故障压缩成一个可回归的场景
- 6. 修复以后,怎样证明问题被解决了?
Agent 为什么会失败?如何定位问题?——从 【OpsArk 运维智能体】的执行链说起
一次 Agent 故障,可能发生在理解需求、准备计划、执行动作或验收结果的任何一个环节。定位的关键,是找到最后一个有证据支持的状态,以及从哪里开始,系统的判断超出了证据。
我开发了一个面向服务器运维场景的桌面应用——OpsArk 运维智能体。
它把服务器连接、远程文件、终端和 Agent 任务处理放在同一个工作台里。用户可以用自然语言提出运维需求,查看生成的执行计划,再由系统按照当前授权规则分步执行,并结合返回的证据检查任务结果。它的需求处理主线是:需求 → 计划 → 审批 → 分步执行 → 校验 → 总结。
下面是 OpsArk 的产品界面。截图中的任务是“检查服务器当前运行状态并给出优化建议”:右侧列出了资源、磁盘、进程、端口等检查步骤,中间展示执行输出;当某一步触发当前授权规则的确认要求时,任务会等待用户确认后再执行。
OpsArk 产品界面:左侧管理远程文件,中间查看 Agent 执行输出,右侧查看分步计划、风险标记与执行记录。图中一个标记为中风险的检查步骤正在等待用户确认。
做这个产品时,我希望用户能够看清三件事:Agent 准备做什么、实际执行了什么,以及哪些证据支持最终结论。这也成为我排查 Agent 故障时的一条主线。
一次 Agent 故障,可能发生在理解需求、准备计划、执行动作或验收结果的任何一个环节。定位的关键,是找到最后一个有证据支持的状态,以及从哪里开始,系统的判断超出了证据。
在 OpsArk 运维智能体的一条历史故障记录里,终端显示:“等待 Agent 向服务器发送命令……”
这个提示很容易让人沿着 SSH、网络或服务器负载排查。但项目的《Agent 执行冲突修复与后续工程化计划》记录了另一条因果链:模型生成了一个工具动作,系统在准备计划时,误给它加上了 Shell 专用的验收字段。随后,工具契约校验拒绝了这个计划。
动作在审批前就被拦截,根本没有派发。
这里同时存在两个问题:执行链内部的数据处理出了错;界面又把这个错误显示成了“还在等待”。如果只看终端,排查方向从第一步就偏了。
本文结合 OpsArk 的代码、历史修复记录和离线回归场景,讨论一套可以复用的 Agent 故障定位方法。
本文以相关历史版本的工作区源码与已有开发记录为依据,不代表已发布安装包的全部行为。文中的“当前实现”指写作时核对的工作区实现;历史故障、离线测试和机制示例会分别标明。下文的分类是一套排查方法,不是失败原因的完整清单或发生频率排名。
1. 先把 Agent 拆成一条可以检查的执行链
OpsArk 是一个智能运维桌面项目。用户提出需求后,系统需要生成计划、校验计划、判断授权,再通过工具或 Shell 操作服务器,并根据执行证据检查目标是否完成。
它还接入了独立知识服务,用历史资料辅助规划。为便于排查,可以把这些能力整理成下面的概念链路。
图 1:面向故障定位的概念分层。知识检索是可选输入,实际执行包含循环与恢复,并非所有任务都严格经过一次线性流程。
每一层都应回答一个不同的问题。下表列出的是常见问题示例,不表示这些问题都已在 OpsArk 中实际发生:
| 层次 | 必须确认的问题 | 典型失败 |
|---|---|---|
| 需求 | 用户要完成什么,还有哪些不能违反的约束? | 补充了“不能重启”,后续计划却遗漏它 |
| 上下文 | 提供的信息适用于当前环境吗? | 用旧版本文档指导新版本服务 |
| 模型提案 | 下一步动作是否合理、完整? | 选错工具、遗漏步骤、返回非法结构 |
| 契约 | 提案是否符合系统可以处理的格式和业务规则? | 字段位置错误、参数非法、工具与 Shell 字段混用 |
| 授权 | 当前批准的目标、动作和范围是否仍然匹配? | 审批后切换服务器,却沿用旧批准 |
| 执行 | 动作有没有发出,实际发生了什么? | 连接中断、环境不支持、执行结果未知 |
| 验收 | 证据是否足以证明全部目标达成? | 命令退出成功,却没有完成部署 |
确定性的格式、身份和授权检查,适合直接交给程序处理;需要理解任务含义的判断,再结合模型与证据完成。格式合法只能说明数据满足相应契约,不能单独证明动作正确或目标能够达成。
Chip Huyen 在《Agents》中讨论了规划、工具使用及其失败模式,并将效率纳入评估。对产品工程而言,还需要沿着这些动作检查系统自己的契约、状态和验收逻辑。参考原文
2. 历史案例:工具提案为什么在准备后被拒绝?
回到开头这次发生于 2026 年 9 月 26 日的故障。
模型返回的动作是server.resolve_connection,属于结构化工具调用。它没有 Shell 命令,但旧的通用准备流程调用了ensureStepValidator(),给步骤添加了 Shell 专用的validator。
工具步骤的契约又明确禁止夹带这些字段。于是,同一个计划在模块之间传递时,先被补字段,再因这个字段被拒绝。
图 2:历史故障的简化因果链。故障发生在计划处理阶段,没有进入实际工具执行。
定位这个问题,需要把三类材料放在一起:
- 模型原始提案:工具动作没有 Shell 验收字段。
- 程序准备后的计划:出现了新增的
validator。 - 契约校验错误:工具步骤禁止这个字段。
差异说明了问题来自哪里:系统自己的准备逻辑改变了动作的结构。
当前代码通过动作类型分流。下面是项目中的实际函数:
exportfunctionnormalizeStepValidation(step:PlanStep):PlanStep{returnstep.action?.type==="tool"?step:ensureStepValidator(step);}这个函数负责按动作类型分流;非法字段的拒绝由计划校验和执行前检查承担。Shell 专用验收器也会拒绝工具步骤进入。对于受字段污染影响的旧待执行计划,修复记录规定保留原始数据、撤销相关审批并要求重新规划,不能通过删除字段自动恢复执行。
界面则根据真实任务阶段显示状态:规划失败、等待审批、等待输入、工具运行,各自有对应提示。
这个案例留下了一条很实用的排查习惯:遇到“模型给错了”的怀疑,先比较模型输出与最终执行计划,确认中间程序改了什么。
3. 回归场景:端口检查成功,是否就可以继续部署?
另一个需要防范的问题,是把“检查执行成功”解释成“变更前提已经满足”。
OpsArk 的一条离线回归测试构造了这样的场景:用户要部署站点,Agent 先检查监听端口,再准备启动服务。检查工具正常完成,返回的结果却显示目标端口已被占用。
测试中工具回执的success为true,观察项的exitCode为0;模拟输出中则存在监听0.0.0.0:8080的进程。这些信息应分开解释:
| 层次 | 这份测试回执支持什么判断? |
|---|---|
| 检查执行 | 按测试回执,端口检查完成并返回了结果 |
| 环境观察 | 模拟的端口清单中存在 8080 监听项 |
| 资源归属 | 还不能仅凭该监听项认定它是目标服务,或认定可以停止它 |
| 后续决策 | 如果新服务需要绑定冲突的地址和端口,应先核对归属、目标与约束,再决定复用、调整或停止执行 |
端口被占用本身不等于整个部署目标无法完成。已有监听可能就是目标服务,也可能需要在授权范围内调整方案。这里能确定的是:取得端口列表,不能直接证明预先拟定的启动动作可以执行。
当前项目在“只读观察 → 后续变更”的边界加入了阶段复核:当前计划中已有满足归属与引用要求的观察回执时,先根据结果重新评估剩余计划,再考虑后续变更。对应的 Store 层回归断言:保留端口回执、进入调整流程,而且不派发预先拟定的启动命令。这个断言验证了流程拦截,不能证明真实模型随后一定会给出正确的调整方案。
这里有一个实现细节:observationBoundary()并不靠解析端口字符串来直接决定能否部署。它识别的是“已有新的观察证据,而下一步将改变环境”这个边界。具体结果仍要进入任务判断;即使返回空端口清单,也不能靠步骤标题里的“确认空闲”直接放行。
而且,检查和写入之间还可能发生变化。执行前重查可以缩小竞态窗口,但要保证检查与变更之间的约束,还需要适合该操作的原子机制、锁或服务端校验。当前这项阶段复核不提供任意 Shell 的原子性保证,项目内的资源协调也不能约束外部操作者。
同样的原则也适用于知识库。Core 在提供给模型的上下文中把检索资料标为untrusted_reference,附带文档版本和引用位置,并提示它们不能充当当前服务器的执行证据或授权。这个标记是上下文约束,不能单独保证模型永远遵守;执行仍须经过实际的权限检查和证据验收。某篇知识说“这样配置后服务可以启动”,只能帮助制定方案;要证明当前服务已经启动,还需要当前环境的观察。
一个结果究竟证明了什么,必须说清楚:检查完成、得到环境状态、满足变更前提、完成用户目标,是四种不同的结论。
4. 长任务停止与执行结果未知,要分开诊断
没有新输出时,先检查进展证据
2026 年 9 月 27 日的修复记录中,有磁盘扫描在几十秒内被停止的情况。停止原因包括模型要求调整方案,以及当时“连续两次继续等待后触发熔断”的规则。
不能把这些停止全部归因为默认的 90 秒超时:记录里的实际停止时刻和触发原因并不相同。
排查这类问题,至少要同时查看:
- 谁触发了停止:用户、模型、执行期限,还是监控规则?
- 进程是否仍在运行,有没有有效且足够新的 CPU、I/O 或进度采样?
- 输出是否可能被管道缓冲?
- 采样是确认空闲,还是根本没有采集成功?
进程仍然存在,也不能单独证明业务有进展。判断是否停滞,需要结合任务类型、采样有效性与明确的进展指标。
当前实现取消了按连续continue次数终止的规则,并区分普通命令与可识别的只读扫描期限;缺失、过期的采样也不会被当成“资源使用为零”。
明确期限仍然有效,也需要响应用户取消和真实错误。这里要修正的是证据解释:文本沉默,只能说明当前输出通道暂时没有收到新文本。此处的执行期限由客户端监控;退出应用不能被解释为远端进程已经停止。
连接中断后,先确定动作有没有生效
再看一个用于说明恢复机制的场景:启动服务的命令可能已经生效,但连接在结果返回前断开。此时界面收到的错误,无法证明服务没有启动。
OpsArk 的执行台账区分succeeded、failed、unknown和not_dispatched等状态,并分别保存动作、物理尝试与结果证据。not_dispatched表达的是已确认本次尝试没有派发;unknown保留结果无法可靠确认的不确定性。恢复时先核对已经发生的事实,再决定下一步。
尤其需要注意两个边界:
- 台账进入
dispatching,表示已经登记派发尝试;如果随后崩溃,仍可能无法确定远端是否收到动作。 - 只读核对确认服务现在满足验收条件,也不能把原来结果未知的历史尝试改写为成功。
此外,failed也不等于“没有发生任何变更”。一组操作可能先完成部分修改,再在后续步骤出错;决定是否重试前,仍要核对已经发生的效果。枚举名也不能代替底层原因:项目对只读与变更操作采用不同的错误分类,某些只读传输错误也会归入failed。
当前项目对部分文件传输和服务操作提供了专用核对路径。任意 Shell 的通用自动对账、补偿与回滚,仍不在这项能力的覆盖范围内。
因此,恢复操作也应该分清对象:重新核验结果、重试保存记录、重新生成计划、重新执行动作,各自会产生不同的影响。
5. 实际定位时,沿着这五步走
图 3:确认仍在运行与失去结果确定性,需要不同的处理方式。日志缺失不能证明动作没有执行;页面转圈也不能证明远端仍在运行。
第一步:固定用户目标与当前版本
记录原始需求、补充约束、模型配置、代码版本、目标服务器和授权范围。例如“检查配置并安全重新加载服务”和“重启服务”,允许发生的动作就不同。
如果用户后来补充了条件,还要检查这些条件是否进入后续规划和验收。不要在复盘时只保留最后一句话。
第二步:串起同一次运行的身份
OpsArk 中可以沿着下面这些身份追查,具体字段分布在任务、台账和证据记录中:
| 身份 | 用途 |
|---|---|
taskId、roundId、stepId | 找到任务、轮次和具体步骤 |
operationId | 找到一个逻辑动作 |
attemptId、executionId | 找到该动作的具体派发尝试及执行通道关联 |
| 目标服务器身份、执行意图摘要 | 确认动作发给谁、批准的内容有没有变化 |
evidenceIds/evidenceRefs与证据作用域 | 找到支持结论的原始结果及其归属 |
这些关联有助于识别误用的证据。上一轮结果如果仍适用于当前需求,可以按规则复用;另一台服务器的结果不能证明当前服务器的状态;迟到结果应保留在原尝试中,由当前任务重新核验适用性。
第三步:找到最早违背约束的变化
按顺序比较:需求与约束 → 模型原始响应 → 程序准备后的计划 → 审批快照 → 派发记录 → 返回结果 → 验收结论。
数据在准备过程中发生变化本身并不代表错误,重点是变化是否违背了动作契约、用户约束或实际观察。开篇故障的关键是新增了工具契约禁止的validator;端口测试则要求系统在看到新的观察后重新判断变更前提。不要停留在最外层的“任务失败”提示。
第四步:根据失败层选择恢复动作
| 已查明的状态 | 合适的下一步 |
|---|---|
| 模型响应格式不合法,动作尚未派发 | 保留原响应与具体字段错误,在预算内修复或重新规划 |
| 计划被程序错误地改写 | 修复准备逻辑,并补充跨模块回归 |
| 授权范围或目标不匹配 | 校正目标与权限,再按规则审批 |
| 已确认只读的工具发生临时网络失败 | 按错误类别有限重试,保留失败记录 |
| 已确认任务仍在运行 | 检查进展、期限与停止条件,避免重复启动 |
| 变更动作结果未知 | 查询台账与现场状态;未核对前不盲目重放 |
| 变更已返回失败 | 核对部分完成与残留状态,再决定修复、补偿或重试 |
| 执行结果已保存,但验收没有完成 | 使用已有证据继续核验,避免重复变更 |
| 证据不足或目标未满足 | 明确指出缺哪项证据或哪项条件,再补充观察或调整方案 |
第五步:把故障压缩成一个可回归的场景
保留足够复现问题的需求、提案、错误、环境条件和结果,去掉凭据及无关资料。写清楚“系统应该做什么”,同时写清楚“绝不能发生什么”。
例如,工具字段污染的回归目标包括:满足契约且授权有效的工具步骤可以通过准备和审批;非法字段仍被拒绝;不会误走 Shell 执行器;读取旧任务本身不会触发重放。
6. 修复以后,怎样证明问题被解决了?
回归检查除了验证局部函数,还应检查任务最终状态和实际派发次数,确认有没有遗漏动作、错误完成或重复副作用。
可以从这些场景开始:
| 注入的情况 | 应保持的行为 |
|---|---|
| 工具提案经过通用计划准备 | 不被补入 Shell 专用字段 |
| 端口探测成功,但目标端口已占用 | 先复核变更前提,不凭“检查成功”直接部署 |
| 长任务没有新文本,但仍有有效进展 | 不单凭文本沉默终止任务 |
| 变更发出后连接中断 | 保留不确定性,先核对,避免重复副作用 |
| 用户取消后旧结果才返回 | 保留历史事实,不推进新计划 |
| 命令成功,但某项用户约束未满足 | 不能宣布整个目标完成 |
更进一步,需要把“系统说完成了”和“独立检查确认完成了”同时记录。Anthropic 在 Agent 评测文章中也区分了执行记录与最终环境状态:预订系统说“已订票”,与数据库里确实存在相应订单,是两种证据。参考原文
这里的独立验收,是依据预先定义的目标与约束检查最终环境,不能仅复述规划模型的完成声明。验收器自身也需要验证;HTTP 200、进程存在等单项信号,是否足够取决于具体目标。
OpsArk 的离线评测汇总器已经区分了两种口径:
目标达成率 = 独立验收通过的运行数 / 全部实际启动的运行数 严格闭环成功率 = 独立验收通过且 Core 宣告整体完成的运行数 / 全部实际启动的运行数这里一次运行包含它的全部内部修复与重试。独立验收裁决为unknown的已启动运行仍在上述分母中,但不计成功;这不等于已经证实它失败,因此还要单独报告未知数量与裁决覆盖率。没有启动的预登记任务另列,不能直接从实验清单中删除;分母为零时,比例应标为不可计算。
这里的验收unknown与执行台账的尝试结果unknown属于不同层次:当前环境可能通过了独立验收,而某次历史尝试的结果仍无法确认。两者应分别保存。
成本统计应包含失败与恢复的费用;有费用缺失时,应报告覆盖率,不能把未知当成零。必要审批和额外人工救场,也需要分别计数。
这个工具负责汇总显式输入的运行记录,不负责证明输入的验收结论真实可靠;仓库附带的是合成样例。要对外报告业务成功率,还需要真实执行样本、固定场景、可信的独立验收与对应版本信息,不能直接从日志里的completed推导生产成功率。
当一个 Agent 再次停住时,可以先回答三个具体问题:最后一个确认发生的动作是什么?哪份证据支持它?从哪一步开始,后续判断失去了可靠依据?
能回答这些问题,一次模糊的“Agent 不好用”,才会变成一个可以定位、修复并持续验证的工程问题。
**延伸阅读:**Anthropic 的 Building effective agents 讨论了通过环境反馈推进任务,以及在需要时暂停、接受人工反馈和设置停止条件,可与本文的执行链一起阅读。