← 所有文章
· 4 分钟阅读

事故复盘:调查并行,判断串行

应用方案软件运维事故复盘根因分析sreincident-responsetypescript
证据可运行 Demo

仓库示例运行在随附的事故情境上。演示的是编排与证据交接;接入真实可观测性系统需要读者自行完成。文中的调度与交接行为来自已发布的运行时 API。

事故复盘常常从排队开始。一个人看日志,然后另一个人查部署记录,接着团队估算客户影响。全都做完之后,才有人开始解释到底发生了什么。

这里的等待大部分是白等的。日志、部署和影响面是三条独立的证据流,本来就可以同时查。真正必须串行的是判断——而这是调度问题,不是靠自觉能解决的问题。

这张图

可运行的事故复盘 DAG 示例用了五个任务:三路调查、一个分析、一个撰写。

await orchestrator.runTasks(team, [
{
title: 'Logs',
description: '从事故日志中提取故障模式。',
assignee: 'log-investigator',
},
{
title: 'Deployments',
description: '把事故时间窗与近期变更做关联。',
assignee: 'deployment-investigator',
},
{
title: 'Impact',
description: '分析影响面。',
assignee: 'impact-investigator',
},
{
title: 'Root cause',
description: '综合三路调查,形成有依据的假设。',
assignee: 'analyst',
dependsOn: ['Logs', 'Deployments', 'Impact'],
dependencyPayload: 'structured',
},
{
title: 'Postmortem',
description: '把证据与假设写成最终文档。',
assignee: 'writer',
dependsOn: ['Root cause'],
},
])

前三个没有声明依赖,所以同时启动。分析任务要等三路都完成。撰写任务要等分析落地。

执行器是事件驱动的:下游任务在自己的依赖被满足时立刻启动,而不是等一整批做完。TaskQueue 发出 task:ready,调度器指派这一个任务,派发闸门检查取消状态、预算状态、审批状态与 AgentPool 容量,任务完成后立即解锁其下游。某条分支先跑完,不会被另一条与它无关的分支拖住。

让证据始终可归属

复盘是一条证据链,不是一段漂亮的说明。如果三路调查的结论最后是以一段混合好的文字送到分析方手里,那拆成三路的意义就没了。

dependencyPayload: 'structured' 就是防这个的。默认情况下,直接依赖注入的是上游任务的原始 output——一段叙述文本,分析方得自己重读、自己归属。设成 'structured' 之后,注入的只有从该依赖成功的 AgentRunResult.structured 派生出的规范 JSON,叙述文本被排除在外。如果叙述本身也有价值,用 'both' 会注入带标签的原始与结构化两段。

于是分析方不需要事后猜某句话来自哪一路。它拿到的是三条校验过的记录,每条都能追回产出它的调查方。

这样分歧才有用。日志可能指向一种故障模式,而部署时间线指向另一处。调和这种张力正是分析方的工作——前提是这份张力在交接中活了下来。

每条分支事后都还查得到

当一个 Agent 执行了多个任务时,agentResults 会把它们合并。对复盘来说这是错误的索引:你要的是日志调查方那一条独立的记录,而不是它做过的所有事情揉在一起。

taskResults 为每个任务保留未经合并的结果,以稳定的任务 ID 为键:

const result = await orchestrator.runTasks(team, tasks)
const logTask = result.tasks?.find(task => task.title === 'Logs')
const logFindings = logTask
? result.taskResults?.get(logTask.id)?.structured
: undefined

两套索引指向同一批执行,暴露 taskResults 不会把 token 用量重复计算。对一次事故复盘而言,这是「模型说了 X」和「日志调查方在任务 1 里报告了 X」之间的区别——三周后再起争议时,能站住的是后者。

把贵的模型用在判断上

三路调查读的是随附证据,而分析方要形成假设,这两类调用的难度并不相同。Model Routing 让它们分开计价:

const modelRouting: ModelRoutingPolicy = {
rules: [
{ match: { agent: 'analyst' }, route: { model: 'claude-opus-4-7' } },
{ match: { phase: 'worker' }, route: { model: 'claude-haiku-4-5' } },
],
}
await orchestrator.runTasks(team, tasks, { modelRouting })

规则按数组顺序求值,第一条命中的胜出,所以更具体的规则要放在前面。没有命中任何规则的调用,仍然用它本来会用的模型。除了 agentphase,规则还能匹配 taskRoletaskPriorityleafhasDependencies——后三个只会命中 worker 与 delegated 调用。

这是 Model Routing,决定的是某次调用用哪个模型。Execution Routing 是另一个决定:这个目标究竟以单 Agent 还是团队的形态运行。

产出物,以及它的边界

示例产出三份调查结果、一个基于它们的根因假设、一份最终复盘文档,以及运行级的耗时与用量。

它不查询任何真实的日志服务、部署平台、状态页或工单系统。情境数据随 recipe 一起提供。这让编排本身保持可检查,也把证据边界老实划了出来。

接真实系统是应用层的工作,而运行时给这些决定准备好了位置:

  • 只读集成从构造上就是只读的:内置工具默认拒绝授权,一个既没声明 tools 也没声明 toolPreset 的 Agent 拿到零个内置工具。
  • 呼叫 on-call、关闭事故这类工具标 consequential: trueonToolCall 会在输入校验之后、execute 之前逐次拦截。
  • onPlanReadyonTaskDispatch 守着另外两条边界:在调查动到生产之前,审批整体形态,或者审批其中一个单元。
  • 链路数据以尽力而为的方式脱敏检测到的敏感信息。事故日志恰恰是这个限定词最要紧的地方——字段级脱敏该在源头做,那是你的工作,不是运行时的承诺。

运行时不会做的事情是猜。高影响判定只看工具授权,不看目标、提示词、工具参数或模型输出。一次措辞骇人但只授权了无害工具的事故,不会被标记。

为什么用显式图而不是协调器

这条工作流不需要谁去发明它的拓扑。五个角色和它们的依赖关系,在事故发生之前就已经定了,所以 runTasks() 是更好的默认:每次都跑同样的证据分支,依赖关系在代码里可审阅,缺失的分支会阻塞综合而不是悄悄消失,互不依赖的分支照样并行。

协调器变得有意思,是在调查计划本身会随事故类型剧烈变化的时候。即便如此,onPlanReady 的存在意义就是让你在它拿到任何权限之前,先把提议的计划读一遍。

跑一遍 recipe

在框架仓库里,配好 ANTHROPIC_API_KEY

Terminal window
npx tsx packages/core/examples/cookbook/incident-postmortem-dag.ts

对着随附情境检查任务记录和最终产物。然后一次只替换一条证据分支,接入你自己系统的只读集成:先 fixture,再只读源,再人工审阅产出,最后才谈更大的权限。


阅读英文原文

// 企业服务

要把它用到生产环境?

open-multi-agent 采用 MIT 许可、可自行免费运行。当你需要在期限内交付、集成,或获得支持时,元定义科技(YuanASI)提供商业交付与支持。

// 直接联系

把 Open Multi-Agent 用进真实业务

联系框架作者本人,帮你梳理 AI 落地目标、让 AI 真正与业务结合

可提供的工程服务
S-01

AI Agent 定制开发

业务梳理、Agent 设计、Prompt 评估、生产部署、私有化与持续支持。

S-02

多智能体系统集成

多 Agent 架构编排、RAG、CRM / ERP / API 对接、性能与稳定性调优。

S-03

企业 AI 咨询

AI 场景评估、技术选型、POC、ROI 估算与落地路线规划。