This starts chapter E3. Its course guide is E3 in the Function E course guide →
Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.
recordings/E3.1.mp4Supervising by re-reading everything the loop did.
Know what the loop must escalate and sample the rest.
Run a triage loop over Wazuh alerts and supervise by exception.
The queue does not go away; it changes shape. Instead of triaging alerts you are supervising something that triages alerts, which is a different skill with a different quality bar and a much worse failure mode: confident, fast, and wrong at volume.
supervise() wraps D1.7's triage with the list of alerts that bypass ranking entirely. A canary read is not a scoring question — D1.8 built it precisely so that it needs no triage — and the sample below the line is what makes the closing rule testable rather than trusted.Supervising a triage loop rather than re-reading it: the list of alerts that bypass ranking entirely, and a sample of what the loop closed, so the closing rule is measured rather than trusted.
Supervising a loop by re-reading everything it did is not supervision, and a loop that closes a true positive does so silently.
Decide what the loop may conclude, what it may do unsupervised, and which sample of its closures a human reads.
Alerts escalated versus closed against ground truth, and the severity floor no automatic closure may cross.
Measure this
| what to measure | denominator or target |
|---|---|
| alerts escalated against those closed, versus ground truth | two rates |
| severity floor no automatic closure may cross | the floor itself |
before after
+------------------+ +---------------------------+
| alert -> analyst | | alert -> loop -> analyst |
| decides | | proposes reviews|
+------------------+ +---------------------------+
100 alerts/day 1000 alerts/day, 40 reviewed
new failure mode: confident, fast, and wrong at volumeThe classic SOC job is a queue: alerts arrive, an analyst reads each one, decides, and moves on. The constraint is human attention, and it does not scale — which is why tier-1 burnout and alert fatigue are structural rather than cultural problems.
The agentic version replaces "read every alert" with "operate a loop that reads every alert". The analyst's job becomes:
The skill that transfers is not triage speed. It is knowing which signals the loop may believe — because a triage loop with a weak verifier closes true positives at machine speed, and closing a true positive is silent.
Anchor → E1.0. The understand interval stops being one analyst's reading speed and becomes the loop's, which moves the work rather than removing it: deciding what the loop may conclude, what it may do unsupervised, and which sample of the rest gets read.
Every triage decision has two error directions and they are not symmetric. Escalating a false positive costs an analyst ten minutes. Closing a true positive costs you the incident, and nothing tells you it happened.
Three rules make an agentic triage loop safe to run, and none of them is about model quality.
The skill scores a triage loop against ground truth, sweeps the confidence bar so the trade between analyst minutes and missed incidents is made explicitly, and adds the severity floor that no automatic closure may cross whatever its confidence.
The skill — skills/detection/triage-loop-with-floor/SKILL.md
name: triage-loop-with-floor
description: >-
Run an alert triage loop against ground truth, measure the confusion matrix,
and bound it with a confidence bar and a severity floor no automatic closure
may cross. Use when automating triage, or when deciding what an agent may
close on its own.
allowed-tools: Read, Grep, GlobA triage loop that matches ground truth on a sample is a promising loop. What makes it something you can run is the pair of bounds around it: a confidence bar that decides when it defers, and a severity floor it may never close through, whatever its confidence.
Before an agent closes anything, and when tuning how much of a queue is automated.
1 — Score against ground truth, as a confusion matrix. Escalated and closed, against true and false. Accuracy alone hides the direction of the errors, and only one direction matters here.
2 — Sweep the confidence bar. For each setting, record analyst minutes saved and incidents missed. This is the trade being made, and it should be made explicitly by whoever owns the queue rather than implicitly by a default.
3 — Set the severity floor separately. Any alert above it is escalated regardless of confidence. A confident wrong closure on a critical alert is the failure this exists to prevent, and no confidence threshold protects against it.
4 — Check what the floor costs. How many alerts it forces to a human per day. If that number is above the reading budget, the floor is theatre and the queue needs a different cut.
5 — Record every automatic closure with its reason and confidence. The loop will be wrong sometimes; the question at review is whether you can find out how.
Input — the fixture committed at the top of scripts/triage_loop_with_floor.py. Edit it and re-run: the buckets, counts and verdicts below are derived from it, not hard-coded.
Output — the opening lines of a real run:
queue: 8 alerts, 4 true positives
A-01 medium impossible travel dana@corp
A-02 critical metadata service access patch-agent
A-03 medium failed logins x40 svc-etl
A-04 high secret path read patch-agent
A-05 high new admin group member sam@corp
A-06 low port scan detected scanner-01
A-07 high egress to unlisted host triage-agent
The run continues past this. test_skills.py executes the script on every build, with no model configured — so what CI proves is that it runs and refuses legibly, not that it prints these lines. The output above is a recorded run against a real model, and nothing re-diffs it: a model's answer is not reproducible, and this repository does not claim otherwise anywhere it can be checked.
{
"confusion": {"tp": 0, "fp": 0, "tn": 0, "fn": 0},
"confidence_sweep": [{"bar": 0.0, "analyst_minutes_saved": 0, "incidents_missed": 0}],
"floor": {"severity": "str", "escalated_regardless": 0, "per_day": 0},
"audit": {"closures_logged": true, "fields": ["reason", "confidence", "rule"]}
}
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 E3.1Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.
Then pick e3-1-from-alert-queue-to-loop-operator:
| agent | install just for this agent | start it |
|---|---|---|
| Claude Code | python3 scripts/install_skills.py --tool claude --lessons E3.1 | type /e3-1-from-alert-queue-to-loop-operator |
| GitHub Copilot (VS Code) | python3 scripts/install_skills.py --tool copilot --lessons E3.1 | type /e3-1-from-alert-queue-to-loop-operator in Chat |
| Cursor | python3 scripts/install_skills.py --tool cursor --lessons E3.1 | type / in Agent chat and search for it |
| Codex | python3 scripts/install_skills.py --tool agents --lessons E3.1 | type $e3-1-from-alert-queue-to-loop-operator, 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 E3.1Neither 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 E3.1 --out work/cybertravels
python3 scripts/checkpoint.py --at E3.1 --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.
The triage loop escalates 4 alerts and closes 4, matching ground truth on all 8. Lowering the confidence bar trades analyst minutes against missed incidents. The severity floor converts any high or critical closure into an escalation, and the closure sampling routes a fraction of routine closures to a human for quality measurement.
Ask your SOC one question: when an incident is confirmed, does anyone check whether an earlier alert about it was closed? If nobody does, you have no measurement of your false-negative rate — with or without an agent.
Tools used
Skill source: skills/detection/triage-loop-with-floor