Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.
recordings/E5.2.mp4The incident closes with a narrative, so the same control gap produces the same incident two quarters later.
A structured root cause record naming the failed control, the detection that should have fired, and the specific change proposed.
Build a root cause record from a reconstructed incident and check it names a control rather than a person.
The postmortem says the on-call engineer missed the alert. It is true, it is useless, and it will be true again next quarter about somebody else. A root cause names a control, or it names nothing that can be built.
RootCause refuses "human error", "a process gap" and "insufficient training" outright, and requires all three fields: the control that failed, the detection that should have fired, and the specific change. Name the thing that would have stopped it whoever was on shift.A root cause record that names a control rather than a person or a narrative, with all three fields required — what failed, what should have detected it, and the specific change proposed.
An incident that closes with a narrative recurs, because nothing in a narrative can be built or measured.
Walk the control chain, mark each present, absent or present-but-wrong, and test that the statement names a control.
A mechanical test: does the root cause name a control? "The engineer missed the alert" is true and fails it.
Measure this
| what to measure | denominator or target |
|---|---|
| root causes that name a control | share; "the engineer missed the alert" is true and fails the test |
control chain for INC-2026-114 status
B2.6 provenance at ingress absent <- first absent = ROOT
B3.1 default-deny on the tool call wrong scope
E2.2 detection: refund w/o approval absent <- contributing
E1.2 drift on vendor tool descs absent <- contributing
E4.4 stop authority in the window PRESENT <- and never reached
a control that exists but is never reached is not a mitigating factor.
it is evidence about the detection in front of it.
statement test: names a control -> usable
names a person -> rejected, however true"The on-call engineer missed the alert" is true and useless — it will happen again next quarter to a different engineer. "We should have been more careful" names no control and no change anyone can make.
A usable root cause names the control that should have caught this and did not, in a form the next change can act on and the KCIs can later measure. The test is mechanical, and it should be: does the statement name a control?
Anchor → E1.0. A root cause naming a person ends no interval, because nothing in it can be built or measured. Naming the control that should have caught this gives the next two lessons something to change and something to re-read.
Three categories, and the third is the one that gets missed.
In the CyberTravels incident, stop authority was present and the incident still ran 194 minutes. A control that exists but is never reached is not a mitigating factor — it is evidence that the detection in front of it was the gap, which is a different finding and a different fix.
The rejections are the lesson. Both rejected statements are true; neither can be built, tested or measured.
The skill — skills/response/root-cause-record/SKILL.md
name: root-cause-record
description: >-
Turn a reconstructed incident into a root cause record that names the control
that failed, and reject statements that name a person or an intention. Use when
closing an incident, when a postmortem produces a narrative instead of a
change, or when the same incident class keeps recurring.
allowed-tools: Read, Grep, Glob"The on-call engineer missed the alert" is true and useless: it will happen again next quarter to a different engineer. "We should have been more careful" names no control and no change.
A usable root cause names the control that should have caught this and did not, in a form the next change can act on and kci-fix-validation can later measure.
At incident close, before the postmortem is written, and in review of any incident whose remediation was "increased awareness" or "additional training".
1 — List every control in the chain, not just the one that failed last. Detection, prevention, authority to stop.
2 — Mark each present, absent, or present-but-wrong. The third category is the one that gets missed.
3 — The first absent control in the chain is the root cause candidate. Later absences are contributing.
4 — Test the statement: does it name a control? If it names a person, a team, or a state of mind, it is rejected. This is mechanical and should be.
5 — Carry the named control forward as a KCI. That is the handover to E5.4 and to the policy change proposal.
Input — a five-control chain and three candidate statements, in scripts/root_cause_record.py.
Output — a real run:
REJECT the on-call engineer missed the alert
-> names no control
ACCEPT no control compared the vendor tool description against the version approved at onboarding
{
"incident": "str",
"root_control": "str",
"contributing": ["str"],
"statements": [{"text": "str", "accepted": true}]
}
Three ways to do this lesson. Pick one. They run the same procedure against the same code — what changes is who drives it, and how much you see.
The agent reads the lesson, explains the idea, fetches the code, runs the check and answers your questions as you go. This is the one to take if you are here to learn.
Install it, once. From the folder you cloned. --all does every agent it finds on your machine:
python3 scripts/install_skills.py --all --lessons E5.2Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.
Then pick e5-2-the-root-cause-record:
| agent | install just for this agent | start it |
|---|---|---|
| Claude Code | python3 scripts/install_skills.py --tool claude --lessons E5.2 | type /e5-2-the-root-cause-record |
| GitHub Copilot (VS Code) | python3 scripts/install_skills.py --tool copilot --lessons E5.2 | type /e5-2-the-root-cause-record in Chat |
| Cursor | python3 scripts/install_skills.py --tool cursor --lessons E5.2 | type / in Agent chat and search for it |
| Codex | python3 scripts/install_skills.py --tool agents --lessons E5.2 | type $e5-2-the-root-cause-record, or run /skills |
No agent, no conversation. The same lesson executes and prints the same readback. Take this one if you have no assistant, if you are reviewing rather than learning, or if you want the result without the teaching around it.
python3 scripts/lesson.py E5.2Neither of the above, and no model needed. The first command writes CyberTravels exactly as it stood at the end of this lesson; the second prints the diff — the lines this lesson added or changed, and nothing from any later one.
python3 scripts/checkpoint.py --at E5.2 --out work/cybertravels
python3 scripts/checkpoint.py --at E5.2 --diffOn Windows, type python wherever these say python3 — and use \ instead of / in the paths. Everything else is identical. A0.0 step 2 explains why.
Ways 1 and 2 need a model; way 3 does not. A signed-in Claude Code CLI needs no API key, and any OpenAI-compatible endpoint works too. With neither, the audit exits 2 and says so rather than inventing an answer. A0.0 sets this up.
Ways 1 and 2 both end with the same readback: What I did, What changed, The number and Read this next. A step that was skipped or refused is reported as exactly that, never as a pass.
If you did the last lesson’s challenge, expect this to stop. Most challenges ask you to edit the code, and ways 1 and 2 will not overwrite your edits: you get stopped, not a failure, with two options — --out for a fresh folder that keeps your work, or --force to discard it and start this lesson clean.
One of three statements accepted. The first absent control in the chain is the root cause; the later absences are contributing.
Take your last postmortem's root cause and run the test on it. If it names a person or an intention, rewrite it as a control.
Skill source: skills/response/root-cause-record