AI 学习笔记(二十三):LLM 事故证据留存与 Postmortem Action Tracking 最小实践

上一篇我们把自动止血、恢复流程和恢复验收门槛拆开了。
但线上事故真正走到收尾时,很多团队还会遇到另一个更麻烦的问题:

系统看起来恢复了,可真正能留下来的事故证据很少,复盘 action item 也没有被持续跟踪。

结果通常会变成这样:

  1. 当时谁切了哪个 fallback,几天后已经说不清
  2. 到底是 promptretrievaltool 还是 provider 出的问题,只能靠回忆拼
  3. 复盘会上列了很多 action item,但一周后没有 owner、没有截止时间、也没有验证结果

所以这篇继续生产运维主线,补上事故治理里很容易被忽略的一环:

把 incident evidence 和 postmortem action tracking 做成可持续执行的工程闭环。

先说结论:

  1. 事故证据不是“复盘时再去找”,而是事故发生时就要结构化留存
  2. postmortem action item 不应该只是一份会议纪要,而应该是一份可跟踪、可验收、可进入发布门禁的任务清单
  3. 如果没有证据包和 action register,团队通常只会记住情绪最强的部分,而记不住真正可复用的工程输入

1. 为什么“证据留存”和“复盘跟踪”必须独立设计

很多团队已经会做 incident response,也会开复盘会。
但真正落地时,问题经常出在两件事上:

  1. 证据分散
    dashboard 截图在聊天记录里,trace 在 A 系统,prompt 版本在 B 系统,工具副作用日志又在 C 系统
  2. 动作失焦
    会议里说了 8 个 action item,但没人知道哪个是防再发,哪个只是临时修补

这就导致两个典型后果:

  1. 事故复盘更像“还原故事”,而不是“验证系统哪里失效”
  2. action item 只能写进文档,却进不了日常工程节奏

更稳的做法,是把事故收尾产物拆成两份:

1) Incident Evidence Package

回答的是:

  1. 事故什么时候开始、什么时候被发现、什么时候被止血
  2. 当时系统到底跑的是哪个模型、哪个 prompt、哪个检索配置、哪些工具
  3. 哪些坏样本、坏副作用、坏路径被真实观测到

2) Postmortem Action Register

回答的是:

  1. 哪些问题需要修
  2. 谁负责修
  3. 什么时候修完
  4. 修完以后靠什么证据证明它真的降低了风险

只有这两份东西同时存在,incident 才算从“处理完成”走向“工程沉淀完成”。

2. 最小事故证据包:至少保留 6 层信息

一个对 LLM 应用足够实用的最小 incident evidence package,建议至少包含下面 6 层:

1) 事故摘要层

最少要有:

  1. incident_id
  2. severity
  3. start_time
  4. detected_time
  5. mitigated_time
  6. verified_time
  7. impact_summary

这层解决的是“这次事故到底是什么”。

2) 请求路径层

至少保留:

  1. request_id
  2. trace_id
  3. route
  4. tenant / workspace / region
  5. release_version

这层解决的是“受影响的是哪条路径、哪批请求、哪次发布”。

3) 模型与 prompt 层

对 LLM 系统来说,这是最容易丢、但又最关键的一层:

  1. provider
  2. model
  3. prompt_versionprompt_hash
  4. temperature / top_p / max_tokens
  5. structured_output_schema_version
  6. fallback_used

没有这层,后面几乎无法判断是:

  1. 模型漂了
  2. prompt 改坏了
  3. schema 严格度变了
  4. fallback 路由把行为改了

4) 检索与上下文层

如果系统接了知识库、RAG 或工具编排,还要保留:

  1. retrieval_index_version
  2. doc_ids / chunk_ids
  3. rerank_strategy
  4. context_budget
  5. tool_search / mcp / plugin 是否启用

很多“回答变差”的事故,本质并不在模型,而在这层。

5) 工具与副作用层

如果请求会调用工具、发消息、写工单、写数据库,这层必须单独留:

  1. tool_calls
  2. tool_arguments_hash
  3. write_side_effects
  4. external_message_ids
  5. rollback_action

否则复盘只能停留在“模型好像做错了”,但说不清楚它到底错写了什么。

6) 止血与恢复层

这层记录的是事故过程中真正执行过的工程动作:

  1. 切了哪个 fallback
  2. 哪个高风险工具被关了
  3. 是否进入 readonly
  4. 哪些 release / prompt rollout 被冻结
  5. 恢复验收用了哪些门槛

也就是说:

事故证据包不只是“问题证据”,还应该是“处理证据”。

下面是一份可以直接落盘成 jsonyaml 的最小结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
incident_evidence:
incident_id: inc-20260328-01
severity: P1
summary:
start_time: "2026-03-28T10:12:00+08:00"
detected_time: "2026-03-28T10:15:00+08:00"
mitigated_time: "2026-03-28T10:26:00+08:00"
verified_time: "2026-03-28T11:04:00+08:00"
impact: "结构化输出路由失败率升至 23%,误触发写工单 14 次"
request_scope:
route: "/assistant/ticket-triage"
release_version: "app-2026.03.28.2"
trace_ids:
- "tr_891"
- "tr_905"
model_context:
provider: "openai"
model: "gpt-5.4"
prompt_version: "triage-v18"
schema_version: "ticket-schema-v4"
fallback_used: true
retrieval_context:
retrieval_index_version: "kb-20260327"
chunk_ids:
- "kb:ops:321"
- "kb:runbook:118"
tool_effects:
tool_calls:
- "create_ticket"
- "notify_slack"
write_side_effects:
- "ticket-1189"
- "ticket-1192"
mitigation:
actions:
- "switch_to_safe_model"
- "disable_write_tools"
- "freeze_prompt_rollout"
recovery_gate: "verified-after-30m-stable-window"

3. 前 30 分钟先固化什么证据

证据留存最怕的是“等开复盘会时再收集”。
因为很多关键信息在 30 分钟后已经开始漂移:

  1. dashboard 时间窗口滚过去了
  2. 新版本已经继续发布
  3. 热修复 prompt 覆盖了旧版本
  4. 工具副作用被人工回滚了

所以更稳的原则是:

事故进入 mitigating 后,先冻结易丢证据,再继续修。

建议前 30 分钟至少完成下面 5 件事:

1) 导出异常请求样本

至少保留:

  1. Top N 失败请求
  2. Top N 高延迟请求
  3. Top N 有副作用的请求

2) 固定模型与 prompt 快照

不要只记版本名,最好连配置一起冻结:

  1. 当前 provider / model
  2. prompt hash
  3. schema 版本
  4. tool allowlist

3) 固定检索上下文

如果系统依赖 RAG,要保留:

  1. 检索索引版本
  2. chunk id 列表
  3. rerank 参数

4) 固定副作用记录

例如:

  1. 发出的通知 ID
  2. 新建的工单 ID
  3. 写回数据库的记录 ID

5) 固定处理动作时间线

把“谁在什么时候做了什么”结构化下来:

  1. 谁冻结了发布
  2. 谁切了 fallback
  3. 谁关了高风险工具
  4. 谁确认了恢复验收通过

很多复盘之所以写成“当时大家感觉可能是……”,就是因为这个时间线没被留下来。

4. Postmortem Action Tracking:别再把 action item 写成散文

证据包解决的是“发生了什么”。
action tracking 解决的是“我们接下来到底要改什么”。

这里最常见的错误,是把 action item 写成下面这种样子:

  1. 优化一下监控
  2. 后续补充回滚策略
  3. 再看看工具权限问题

这种写法几乎不可执行。
一个可跟踪的 action item,至少要有下面 7 个字段:

  1. id
  2. type
    detective / preventive / corrective / governance
  3. problem_statement
  4. owner
  5. due_date
  6. verification
  7. status

其中 verification 很关键。
因为 action item 的目标不是“有人说做了”,而是“系统里真的多了一条防线”。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
postmortem_actions:
- id: pm-001
type: preventive
problem_statement: "高风险写工具在 schema 漂移时仍可自动执行"
owner: "workflow-platform"
due_date: "2026-04-02"
verification:
- "新增 PreToolUse deny hook"
- "回归用例 schema-drift-03 必须失败并阻断写操作"
status: in_progress
- id: pm-002
type: detective
problem_statement: "现有看板无法按 prompt_version 聚合异常"
owner: "observability"
due_date: "2026-04-01"
verification:
- "新增 prompt_version 维度看板"
- "事故演练中能在 5 分钟内定位异常版本"
status: todo

4.1 先按类型拆,而不是按部门拆

一个很实用的拆法是:

  1. corrective
    修这次已经暴露出来的直接问题
  2. preventive
    防止同类事故再发生
  3. detective
    让下次更早发现
  4. governance
    把规则接进权限、发布、审批、审计

这样比“前端 2 条、后端 3 条、平台 1 条”更容易判断优先级。

4.2 action item 要和证据包挂钩

每个 action item 最好都能指向一个明确证据:

  1. 哪个 trace 暴露了问题
  2. 哪个坏样本证明当前系统会出错
  3. 哪个副作用记录说明风险真实存在

否则 action item 很容易退化成“经验性优化”,优先级不断下滑。

4.3 高优 action item 要么入门禁,要么显式接受风险

对于 P1 / P2 事故里暴露出的高风险问题,不能停留在“排期中”。
更稳的做法只有两种:

  1. 接进发布门禁
  2. 显式写下风险接受说明

这能避免团队在下次发布时重复踩同一个坑。

5. 一段可直接接到服务层的最小实现示例

下面这段 Node.js 示例演示两件事:

  1. 把 incident snapshot 收敛成 evidence package
  2. 根据事故特征自动生成最小 action register
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
function buildEvidencePackage(snapshot) {
return {
incidentId: snapshot.incidentId,
severity: snapshot.severity,
summary: {
startTime: snapshot.startTime,
detectedTime: snapshot.detectedTime,
mitigatedTime: snapshot.mitigatedTime,
verifiedTime: snapshot.verifiedTime,
impact: snapshot.impact,
},
requestScope: {
route: snapshot.route,
releaseVersion: snapshot.releaseVersion,
traceIds: snapshot.traceIds,
},
modelContext: {
provider: snapshot.provider,
model: snapshot.model,
promptVersion: snapshot.promptVersion,
schemaVersion: snapshot.schemaVersion,
fallbackUsed: snapshot.fallbackUsed,
},
retrievalContext: {
retrievalIndexVersion: snapshot.retrievalIndexVersion,
chunkIds: snapshot.chunkIds,
},
toolEffects: {
toolCalls: snapshot.toolCalls,
writeSideEffects: snapshot.writeSideEffects,
},
mitigation: {
actions: snapshot.mitigationActions,
recoveryGate: snapshot.recoveryGate,
},
};
}

function buildPostmortemActions(snapshot) {
const actions = [];

if (snapshot.writeSideEffects.length > 0) {
actions.push({
id: "pm-prevent-write-side-effect",
type: "preventive",
owner: "workflow-platform",
dueDate: "2026-04-02",
verification: [
"disable_write_tools_when_schema_failure_rate_gt_threshold",
"regression_case_schema_drift_blocks_write_action",
],
status: "todo",
});
}

if (!snapshot.promptVersionDimensionVisible) {
actions.push({
id: "pm-add-prompt-version-observability",
type: "detective",
owner: "observability",
dueDate: "2026-04-01",
verification: [
"dashboard_groups_by_prompt_version",
"incident_drill_can_locate_bad_prompt_in_5m",
],
status: "todo",
});
}

return actions;
}

这段代码不追求复杂,重点是把两个产物分开:

  1. evidence package 回答“到底出了什么事”
  2. postmortem actions 回答“后面要做什么改动”

6. 怎么把它接进发布门禁

如果 incident evidence 和 postmortem actions 最终没有进入日常发布流程,它们很快又会退化成一次性文档。

更实用的方式,是为 P1 / P2 事故补两条简单规则:

1) 没有证据包,不允许关闭 incident

至少要求:

  1. 有结构化时间线
  2. 有坏样本
  3. 有模型 / prompt / 检索 / 工具证据

2) 没有高优 action 的验证结果,不允许恢复高风险变更

例如:

  1. 高风险写工具暂不恢复自动执行
  2. 相关 prompt rollout 继续冻结
  3. 相关发布需要手动审批

可以先从一个很小的检查器开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export function validatePostmortemReadiness(report) {
if (!report.evidencePackage) {
return { pass: false, reason: "missing-evidence-package" };
}

const hasTimeline = Boolean(report.evidencePackage.summary?.detectedTime);
const hasBadSamples = (report.evidencePackage.requestScope?.traceIds || []).length > 0;
const hasHighPriorityAction = (report.actions || []).some(
(item) => item.type === "preventive" || item.type === "detective"
);

if (!hasTimeline || !hasBadSamples || !hasHighPriorityAction) {
return { pass: false, reason: "postmortem-not-actionable" };
}

return { pass: true, reason: "ready-for-closeout" };
}

这样一来,postmortem 就不只是“写完发群里”,而是真的进入系统约束。

7. 一周落地建议:先把证据结构和 action 模板定下来

如果你想一周内落地,建议按这个节奏推进:

  1. Day 1
    定义 incident evidence 的最小字段:时间线、trace、model、prompt、retrieval、tool side effects
  2. Day 2
    在 incident runbook 里加入“前 30 分钟证据冻结”步骤
  3. Day 3
    固定 postmortem action 模板:type / owner / due_date / verification / status
  4. Day 4
    把高优 action 接进发布门禁或显式风险接受流程
  5. Day 5
    用一次故障演练验证:团队能不能在 15 分钟内收齐证据包,能不能在复盘后一周内追到 action 落地证据

不要一开始就追求特别完整的平台。
先做到证据可留、动作可跟、验证可查,incident 治理质量就会明显提高。

总结

自动止血和恢复验收解决的是“系统先稳定下来”。
incident evidence 和 postmortem action tracking 解决的是“团队下次别再靠回忆救火”。

如果这周你只做一件事,我建议优先完成:

给每一次 P1 / P2 事故固定一个 evidence package 模板,再要求每个高优 action item 都带 owner、截止时间和验证方式。

这样复盘才会真正变成工程资产,而不是一篇情绪还原。

参考资料

本文永久链接: https://www.mulianju.com/learning-notes/ai-learning-notes-llm-incident-evidence-postmortem-action-tracking/