关于 write-a-postmortem
write-a-postmortem 是用于编写基于证据、避免归责的事故复盘 Agent 技能。它指导团队在 OpenKnowledge Markdown 工作区中整理时间线、检索历史事故、分析根因并制定后续行动。
主要功能
- 01避免归责的事故分析
- 02带证据来源的时间线
- 03检索相关历史事故
- 04区分触发因素、症状与根因
- 05行动项与运行手册草案
适用场景
- 01依据监控和部署记录记录故障
- 02判断事故是否重现已知故障类型
- 03将促成因素转化为可验证的后续行动
MCP 相关
该技能通过 OpenKnowledge MCP 读取和搜索范围内的 Markdown、创建或编辑复盘文档并检查文档链接。源代码、日志、仪表板和聊天记录则通过宿主原生工具读取。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ inkeep/ open-knowledge-skills --skill write-a-postmortem安装后在 Claude Code 中输入/write-a-postmortem确认已加载。
详细介绍
撰写事后复盘
平台 /open-knowledge skill 仍然规定这里的所有 Markdown 操作,包括文件夹范围、读写工具范围、链接和预览。此 skill 在此基础上加入事后复盘的撰写规范;它不会取代这些规则。
事后复盘不是状态更新,也不是追责清单。它是基于证据,对单次事故进行持久、无责备的还原,让系统的失效模式清晰可见,并产出有人会实际验证的行动项。只有当具有相同子系统的事后复盘彼此链接时,价值才会不断累积——这正是让重复出现的问题不再被忽视的方式。
此套件会在项目根目录下创建 proposals/、decisions/、specs/、postmortems/ 和 guides/。事后复盘存放在 postmortems/,每起事故一个文件,文件名为 YYYY-MM-DD-name.md,模板 id 为 postmortem。适合整理为指南的行动项会在 guides/ 中生成 runbook 占位文件。
知识库是由 OpenKnowledge MCP 管理的 Markdown 文档。使用 exec(exec("ls -A postmortems/")、exec("cat postmortems/2024-03-02-auth-outage.md")、exec("grep -rln failover ."))和 search({ query: "..." }) 读取和列出范围内的 Markdown;使用 write 和 edit 创建和编辑;使用 links 检查链接图。绝不能对范围内的 Markdown 使用原生 Read/Edit/Grep/Glob/cat。源代码、日志、仪表板、部署历史和聊天记录不属于知识库——请使用宿主提供的原生工具读取。链接使用普通 Markdown 相对链接 [db failover](./guides/db-failover.md)——绝不使用反引号,也不使用 HTML 锚点。
步骤 0 — 无责备检查关卡(先说明,因为它约束后续每个步骤)
无责备是一项必须严格遵守的规范,而不是贴在文档标题处的一句口号。如果无法严格遵守这些规则,那么无论文档开头怎么说,它都不是无责备的。
- 写系统和角色,绝不写个人。 写“部署流水线”“值班工程师”“发布流程”——不要写“Alice”,也不要写“新员工”。点名某个人会让读者产生防御心理,分析也会就此止步。
- 描述系统提供的条件,而不是个人的行为。 写“部署流水线允许未经审核的配置进入生产环境”——不要写“Alice 部署了未经审核的配置”。句子的主语应是导致该结果成为可能的系统。如果句子的主语是某个人,就改写,直到主语变成系统。
- 将每个人的行为都视为当时信息条件下合理的选择。 没有人因为粗心而导致事故。有人根据当时掌握的信号做出了合理的选择。分析要问的绝不是“他们为什么那么做?”,而是“是什么让当时看来那是正确的做法?”仪表板显示绿色、告警从未触发、运行手册明确要求这么做。找出让这种选择显得合理的因素;那就是促成因素。
严格关卡。 如果用户的表达意在追责——“写清楚 Alice 是怎么搞坏生产环境的”“记录是谁把部署搞砸了”——不要照做。请明确说明,并将问题重新聚焦于系统,然后再开始撰写。改写示例:“我会以无责备的方式撰写——有价值的问题不是谁推送了配置,而是什么让未经审核的配置进入了生产环境。这才是能防止事故重演的发现。”仅仅因为用户要求,就撰写一份点名责任人的文档,是此 skill 可能犯下的最严重错误;它会毒害这份文档本应建立的事后复盘文化。
第 1 步 — 先收集证据,再叙述
不能凭记忆编写时间线,然后称之为事后复盘。先收集,再叙述。
使用主机的原生工具(这些工具位于知识库之外)提取:
- 告警和监控 — 哪些告警触发了、何时触发,以及哪些本应触发却没有触发。
- 部署和发布历史 — 事发前几小时发布了什么,以及确切的提交/配置。
- 仪表板和指标 — 错误率、延迟、饱和度,以及显示故障开始和恢复情况的图表。
- 聊天记录和事故频道 — 人员决策的时间戳,以及当时的决策依据。
- 事发时的代码和配置 — 而非当前状态;检出或读取事故 SHA 对应的状态。
硬性要求 — 时间线中的每一项都必须有可指明的来源。 每一行时间线都要注明证据:告警 ID、部署时间戳、图表或聊天消息时间。如果你根据某人的回忆还原了某个时刻,却没有任何记录,仍可将其写入,但要在行内标注:(根据回忆还原,无记录)。时间线悄然混杂记录事实和记忆的事后复盘,比坦承信息缺失的复盘更糟,因为读者无法判断哪些数字可信。
第 2 步 — 查阅同一子系统的过往事后复盘
动笔之前,先确认这类问题是否已经发生过。
exec("ls -A postmortems/")— 一览所有过往事故。search({ query: "<subsystem> <failure mode>" })— 对受影响组件进行语义匹配(例如“数据库故障转移复制延迟”)。exec("grep -rln <subsystem-keyword> postmortems/")— 按服务、错误和机制精确搜索相关术语。- 对最接近的 1–3 个结果执行
exec("cat postmortems/<candidate>.md")— 阅读其中的根因和行动项。
如果这是已记录过的同类问题再次发生,那么这就是整份文档中最重要的发现,应该写在摘要里,而不是埋在“相关信息”中。 问题再次发生意味着先前的行动项未完成,或虽已完成却未能防止复发。请在前两句话中说明:“这是支付服务第三次发生连接池耗尽事故(见 2024-01-11、2024-03-02);第二次事故的行动项始终未完成。”事后复盘的价值,尤其体现在揭示同类问题的反复发生——要醒目地指出这一点。
第 3 步 — 使用模板创建事后复盘
write({ document: { path: "postmortems/YYYY-MM-DD-name.md", template: "postmortem" } })
- 日期应为事故发生日期,而不是今天。 如果故障发生于 2024-03-02,即使你一周后才撰写,文件名也应为
postmortems/2024-03-02-name.md。 name是受影响系统和故障的简短 kebab 格式名称:payments-pool-exhaustion、auth-cert-expiry、search-index-corruption。postmortem模板提供 SRE 结构框架:摘要 / 时间线 / 根因 / 做得好的方面 / 行动项。请按以下步骤填写各部分;不要另行设计结构。
第 4 步 — 摘要:先写影响,再写原因
读摘要的人可能永远不会读其余部分。面向整个组织来写,而不是只面向当班工程师。
- 先说明用户可见的影响: 哪些人受到影响、受到何种影响、持续了多久。“在 47 分钟内,约 12% 的结账请求因 500 错误而失败。”不要写“连接池达到饱和。”
- 然后用一句话说明原因, 简明扼要:“配置更改将连接池大小减半,而当时流量正处于峰值;连接池耗尽,请求排队时间超过超时阈值。”
- 如果第 2 步发现同类问题再次发生,应在此说明(见第 2 步)。
以技术问题开头(“连接池在 14:03 达到饱和”)并把影响埋在后面的摘要,是写给工程师看的,而不是写给需要评估事故的组织看的。影响是最重要的信息;请先用通俗的语言说明影响。
第 5 步 — 时间线:带时间戳、有来源,并明确检测延迟
按时间顺序重建事件。每条记录都应包含:带时区的时间戳、发生了什么,以及证据来源。
- 13:52 UTC — Config change #4821 merged, halving `pool.max` from 200 to 100. (deploy log, PR #4821)
- 14:03 UTC — Checkout error rate crosses 5%. (Grafana, checkout-errors panel)
- 14:19 UTC — First page fires to on-call. (PagerDuty incident #9917)
- 14:26 UTC — On-call acknowledges, begins investigating a suspected upstream issue. (incident channel)
- 14:41 UTC — Config change identified as cause; rollback initiated. (incident channel)
- 14:50 UTC — Error rate returns to baseline. (Grafana)
- 明确指出检测时间和缓解时间。 标出事件开始、被检测到和被缓解的记录。
- 事件开始到检测之间的间隔,通常是文档中最值得采取行动的数字。 本例中:14:03 开始,14:19 首次告警——盲区长达 16 分钟。紧接时间线后,用文字说明这一间隔:“从开始到检测滞后了 16 分钟;结账错误率没有触发告警,只有主机 CPU 触发了告警。” 这句话往往是最能带来成效的改进行动项的来源。
- 保持记录客观,并注明来源。分析应放在根因部分,而不是这里。
第 6 步 — 触发因素与根因、症状
先向读者说明这三个定义,再清晰地区分它们。混淆这些概念是复盘无法提出有效改进措施的最常见原因。
- 症状 — 观察到的现象。“结账请求返回 500 错误;连接池显示没有可用连接。” 症状是真实存在的,但它不是原因;修复症状(重启、扩容)可以结束这次事件,却无法防止下次发生。
- 触发因素 — 引发事件的具体因素。“配置变更 #4821 在流量高峰期将连接池大小减半。” 触发因素是事件经过中不可或缺的一环,但很少是全部原因。
- 根因 — 使触发因素足以导致服务中断的条件。不要停留在“推送了错误配置”上——要追问,是什么导致错误配置能够在高峰期未经保护就推送到生产环境。根因通常是缺少防护措施,而不是触发事件的操作。
追查表面原因之后的因素。 如果你的答案是“有人推送了错误配置”,你只指出了触发因素,然后就停下了。继续追问:什么导致未经审核的配置进入生产环境?为什么没有对连接池大小设置范围校验?为什么金丝雀发布没有发现问题?“五个为什么”等技巧有助于持续追问——但它们只是提示方法,不是证据;而且它们往往会引导你沿着单一因果链追查,实际情况却可能涉及多条链路。大多数事件都有多个促成因素和一两个根因,而不是一条线性的因果链。指出一两个这样的条件:如果这些条件不存在,触发因素就不会造成影响。
第 7 步 — 促成因素
这些条件扩大了影响范围、延迟了检测,或拖慢了恢复。它们不是根因,但正是这些因素导致事件变得如此严重——而且每个因素通常都应对应一个独立的改进行动项。
重点检查以下方面:
- 告警缺失或路由错误 — 第 5 步中发现的检测延迟几乎总会归入这一类。
- 运行手册缺失或过时 — 没有记录恢复流程,值班人员只能临场应对。检查
guides/及其中的last_verified日期。 - 职责归属不明确 — 无人知道哪个团队负责故障组件,因此确认收到告警有所延迟。
- 过时的指南 — 运行手册建议采取反而会加重问题的操作,或者其
last_verified日期已过了很久。 - 叠加条件 — 流量高峰、同时发生的发布冻结、已有另一位值班人员收到告警。
将每个因素分别列出。“沟通本可以做得更好”之类的笼统说法不算因素;“支付连接池没有明确的负责人映射,因此首次告警被发给了错误团队,延误了 7 分钟”才算。
步骤 8 — 哪些方面做得好(实事求是)
这一部分不是凑数,悄悄跳过它是一种失败模式。总有某些因素减轻了损失;把它们写出来,才能让组织保留下来。
- 是什么让问题更快被发现或恢复?是出色的仪表板、快速回滚路径,还是清晰的升级流程?
- 哪些东西在负载下经受住了考验?是能平稳卸载负载的断路器,还是吸收了流量的只读副本?
- 哪项以往的行动项在这次事件中发挥了作用?如果之前的复盘所采取的措施限制了此次事件的影响,就明确写出来并附上链接——这是为行动项争取资金支持的最有力论据。
如实书写。如果确实没什么做得好的,就直说,而不是硬凑赞美——“恢复缓慢且依赖手动操作;唯一有效的是只需一条命令就能回滚。”保留行之有效的做法,与修复问题同样重要。
步骤 9 — 行动项:明确负责人、日期和验证方式
没有负责人的行动项只是愿望。每项都必须具备以下三点,否则就不要列入清单:
- 负责人 — 一个职务或团队(不归咎于个人:“支付团队”,而非某个人的名字,除非此人主动认领)。
- 截止日期 — 一个具体日期,而不是“尽快”。
- 可验证的完成条件 — 可以检查并明确判定是否完成的事项。“当结账错误率连续 3 分钟超过 2% 时触发告警,并在预发布环境中通过主动触发进行测试”——而不是“改进告警”。
根据每项行动能带来的效果进行分类:
- 预防 — 阻止同一问题再次发生(在配置流水线中添加连接池大小校验)。
- 缓解 — 即使问题再次发生,也能限制损失(连接池耗尽时自动卸载负载)。
- 检测 — 更早发现问题(步骤 5 中缺失的错误率告警)。
一份健康的复盘应包含这三类行动项;如果清单里全是预防措施,往往意味着检测和缓解方面的缺口没有得到审视。
符合指南结构的行动项会生成运行手册的初始版本。 如果行动项是“编写/修复 X 的恢复运行手册”,不要只列出名称——现在就先创建初始版本:
write({ document: { path: "guides/db-pool-exhaustion-runbook.md", template: "runbook" } })
在初始版本中预先填写症状(如何再次识别此问题——“结账请求返回 500,且连接池的空闲连接数为零”)和相关时间线摘录(从步骤 5 中摘取实际奏效的回滚步骤)。然后在行动项中链接该初始版本:- [ ] Payments team, by 2024-03-16: complete the [pool-exhaustion runbook](./guides/db-pool-exhaustion-runbook.md), verified by a tabletop dry-run. 运行手册包含 last_verified frontmatter,因此内容过期时会在审核中显现。
步骤 10 — 添加链接并验证
- 添加
## Related部分,链接所有共享同一子系统的既往复盘(来自步骤 2),以及相关的decisions/和specs/——例如设定配置策略的决策,以及故障组件的规范。[2024-01-11 payments pool](./postmortems/2024-01-11-payments-pool.md)、[connection-pool sizing decision](./decisions/2023-11-04-pool-sizing.md)。 - 在步骤 9 中创建的运行手册初始版本,要从相应行动项(如上所示)和
## Related中添加链接。 - 如果你修改的文件夹有索引(例如
postmortems/中的INDEX.md),请更新它的索引,让新事件显示在其中——预览内容便会成为实时进度条。 - 验证:
audit({ path: "postmortems/YYYY-MM-DD-name.md" })应无问题(包括所有 lint 违规和失效的内部链接)——修复每一项发现的问题。确认 frontmatter 完整,并且 Summary / Timeline / Root cause / What went well / Action items 各部分均已填写,没有任何模板占位符。
步骤 11 — 回顾
用以下四点简洁地结束对话:
- 影响:用一句话描述用户可见的影响。
- 根本原因:用一句话说明根本原因(即导致问题的条件,而非触发因素)。
- 最具杠杆效应的一项行动——通常是弥补检测缺口或补上缺失的防护措施。
- 这是否属于重复出现的问题类型——如果是,说明之前的修复为何未能奏效。
然后指出文件:postmortems/YYYY-MM-DD-name.md,以及创建的任何运行手册草稿。
非目标
- 不要归咎于个人。 关注系统和角色,绝不针对个人。如果故事离不开某个姓名,说明故事的框架有问题——应找出系统层面的机制。
- 不要止步于第一个看似合理的原因。 “有人推送了错误配置”是触发因素。继续追问是什么让这个触发因素足以造成问题,直到找到缺失的防护措施。
- 不要写无法验证的行动项。 没有负责人、日期和可核验的完成条件,就只是愿望,而不是行动项。删除它,或补充完整。
- 不要悄悄略过“进展顺利的方面”。 略过会让值得保留的做法失传。如果进展顺利的方面很少,就如实说明,不要删掉这一节。
- 不要用事后复盘重新争论设计决策。 事后复盘记录发生了什么以及运营层面需要做哪些改变。关于架构是否应该有所不同的争论,应放在提案(
/frame-a-proposal)或设计评审(/review-a-design)中——在“相关内容”中添加链接,不要在这里争论。 - 不要编造时间线。 每条记录都必须有可指出的来源;根据记忆重建的记录必须在行内标明。