Cyber Commons · Course guideEvery detection has a subject, and a clock
Your SOC was tuned for people. One hour of an agent is roughly 1,400 tool calls across 260 resources; one hour of a person is twelve actions. Every threshold, baseline and playbook you own was set against the second number, and four of the nine things an agent does in an ordinary day are seen by none of the four sensor classes you already bought.
Build it in five phases, on open source you can run: discover with the sensors you have plus agent telemetry that no product emits, detect against a lake tiered by the queries you actually run with rules mapped to MITRE, understand with bounded investigation and correlation across the fleet, respond with tiers derived from reversibility, and recover with replay, root cause and a policy diff.
Five intervals, one per phase, and all five are numbers: time to discover, to detect, to understand, to contain, and to recover to target. CyberTravels' detection interval is 194 minutes against a 15-minute target and its manual containment finishes in 34 minutes against a 29-minute breakout. Every lesson says which interval it shortens, or which one it spends on purpose.
Taught duration: about 8 hours (Function A's pace is 16 lessons in 4 hours)
Primary audience: Security operations · SOC analysts · detection engineers · incident responders
Prerequisites: Function A, which builds the system this function is about — at minimum A0.0 and A1.0
Reading: 12,134 words of lesson prose, at 180 words a minute.
Four lessons. E1.0 sets up the clock the whole function is anchored to, and the other three are the discover interval: what an EDR, DLP, CSPM or CNAPP estate actually sees when the actor is an agent, what drift does to a control that was signed off against behaviour that no longer exists, and how to find the agents nobody registered.
Six lessons on the detect interval. Where agent telemetry lands, then two chapters' worth of argument compressed into four lessons: a detection whose subject is the agent, one whose subject is the platform, one written by a loop and shipped by a human, one generated from an incident and decided by a benign corpus — and the honeypot, which is the detector with no threshold.
Ten lessons, the longest chapter in the function, on the understand interval. It splits cleanly: E3.1–E3.4 are the operator's shift — going from an alert queue to running a loop, bounding what the investigating agent may touch, the context that makes triage work, and the three instincts that misfire when the actor is an agent.
Five lessons on the respond interval, and the chapter where the arithmetic is most brutal. What may be done without asking, three runbook tiers at three different speeds, containment at machine speed, who holds stop authority and how long they take to use it, and the fleet kill switch.
Six lessons on recover and root cause: reproducing a run you can defend, a root cause record that names a control, picking the layer the fix belongs in, re-measuring the indicators the incident moved, proposing the policy change and saying what it does not fix, and the regulatory clock — which starts at awareness, not confirmation.
Security operations. SOC analysts, detection engineers, incident responders, threat hunters and DFIR leads — running a SOC that now has agents in it, and agents watching it. What changes is the analyst's job: directing a loop instead of reading alerts one at a time.
Use this on your own, or to run Function E for other people. Everything here serves both; the shaded blocks marked For a room are the only parts a solo reader can skip.
Every per-lesson fact below is generated from the lesson sources and the skills — risk, control, the lab, Day 0/1/2, the measure, the command, and each skill's own committed example run. Unlike the Function A guide, it does not carry a hand-verified transcript of every run: sixteen of those were checked by running them, and writing 133 more without running them would be making them up. Trust the run over this page, and correct scripts/exercises/guides.py.
| On your own | Chapter by chapter, in order, and re-read E1.0 before each one — the anchor on every page refers back to it and the function stops cohering if the clock is not in mind. E3 is the longest chapter and splits naturally after E3.4. |
|---|---|
| With a room | Put the five intervals on the board on the first slide and mark which one each lesson shortens. A room that loses the clock reads this function as thirty-one separate detections. |
| Splitting it up | Anything over about four hours is normally split across sittings, and the chapter boundaries are the splits |
| The one sentence to hold | Every detection has a subject, and a clock |
| Chapter | Lessons | What it does |
|---|---|---|
| E1 Discover — the Sensors, and the Agent-Shaped Hole in Them | E1.0 · E1.1 · E1.2 · E1.3 | 4 lessons |
| E2 Detect — the Lake, and Rules Mapped to MITRE | E2.1 · E2.2 · E2.3 · E2.4 · E2.5 · E2.6 | 6 lessons |
| E3 Understand — Correlation, Intel and the Hunt | E3.1 · E3.2 · E3.3 · E3.4 · E3.5 · E3.6 · E3.7 · E3.8 · E3.9 · E3.10 | 10 lessons |
| E4 Respond — From a Conclusion to the Actor Stopped | E4.1 · E4.2 · E4.3 · E4.4 · E4.5 | 5 lessons |
| E5 Recover and Root Cause — From Stopped to Back at Target | E5.1 · E5.2 · E5.3 · E5.4 · E5.5 · E5.6 | 6 lessons |
Half the time? E1.0 and E1.1, then E2.1 and E2.2, E3.1 and E3.4, E4.3 and E4.4, and E5.2. That is one lesson or two per interval and the clock still closes.
E3.5–E3.10 run well as a single discussion of what an investigating agent may touch and what it must not, rather than six labs.
The same three as every function. Way 1 is the default; the other two matter because a machine with no model configured can still do every lesson, and because "what actually changed?" is the question that comes up most.
| Way | What it does | Needs a model? | Use it for |
|---|---|---|---|
| 1 — in your AI agent | The agent reads the lesson, teaches it, runs the check | No — Claude is the agent | The default for every lab |
2 — scripts/lesson.py <id> | Runs the lesson, prints a five-heading readback | Yes, or it exits 2 saying "no model configured" | Worth doing once in A0.0, to meet that message on purpose |
3 — scripts/checkpoint.py --at <id> --diff | Shows the code this lesson added | No | Any "what actually changed?" question |
This function is about a system Function A builds. You do not have to have built it — a checkpoint writes it for you — but you do need it on disk.
python3 scripts/doctor.py, or python scripts\doctor.py on Windows.python3 scripts/checkpoint.py --at E1.0 --out work/e writes the tree with everything taught up to here and nothing taught after.python3 scripts/install_skills.py --all --lessons E.Read this before the first lesson. A taught session opens with it; alone, read it and move on.
Five chapters that are five intervals of one clock. E1.0 sets it up and every lesson in the function is anchored back to it: discover, detect, understand, respond, recover. Each chapter shortens one interval, and the anchor line on every page says which.
The agent-shaped problem runs through all five. A sensor estate built for people sees an agent as either a volume anomaly or nothing at all; an alert queue built for one analyst reading one alert does not survive a fleet; a containment plan built around disabling an account does not stop a workload holding an issued token. Each chapter is the same correction applied to a different interval.
The one sentence to keep returning to is that a detection without a named subject is a volume rule. E2.2's subject is the agent, E2.3's is the agent platform, and E3.4 is the lesson on the three instincts that misfire when the actor turns out to be an agent rather than a person.
Each lesson's risk, control, days, measure and skill — and, folded, what that skill prints.
Four lessons. E1.0 sets up the clock the whole function is anchored to, and the other three are the discover interval: what an EDR, DLP, CSPM or CNAPP estate actually sees when the actor is an agent, what drift does to a control that was signed off against behaviour that no longer exists, and how to find the agents nobody registered.
E1.0 is the most load-bearing lesson in Function E. Every later page carries an anchor line back to it.
Do not shorten E1.0. The anchors on the other thirty pages stop meaning anything without it.
A detection stack tuned for human tempo, watching an actor that acts a thousand times an hour and never repeats a session.
Agent telemetry as a first-class data source, detections written for agent behaviour, and a stop lever that a human can actually pull in time.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| A detection stack tuned for human tempo, watching an actor that acts a thousand times an hour, reports nothing and means nothing. | Learn the five intervals and the open-source stack that measures them, before building anything. |
Day 2 — measure. the five intervals themselves (discover, detect, understand, contain, recover — in minutes; every later lesson moves one)
The lab. Put one agent trace and one human session side by side and list what separates them.
agent-tempo-baselinepython3 skills/detection/agent-tempo-baseline/scripts/agent_tempo_baseline.pyIts output contract requires subjects, ratios, existing_rule, recommendation. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
agent-tempo-baseline prints — its own committed example run Alex 12 actions/hour -> silent
workflow agent 1400 actions/hour -> ALERT
the agent crosses the threshold after 154 seconds
and keeps going for the remaining 3446.
So the rule fires, about two and a half minutes into a sixty-minute run -
by which point most of what the actor was going to do is done. If thoseFour products are bought, the estate is assumed covered, and the agent's whole working day falls between them.
A coverage matrix computed per sensor and per agent action, with the uncovered actions named rather than counted.
| Day 1 — what you do |
|---|
| Score each sensor class on visibility rather than alerting, per agent action, and read the rows nothing covers. |
Day 2 — measure. ordinary agent actions seen by no sensor class at all (four of nine on the sample; an architecture finding, not a tuning backlog)
The lab. Score four sensor classes against nine real agent actions and read the column none of them covers.
sensor-coverage-matrixpython3 skills/detection/sensor-coverage-matrix/scripts/sensor_coverage_matrix.pyIts output contract requires sensors, actions, per_sensor, combined, uncovered. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
sensor-coverage-matrix prints — its own committed example runper-sensor coverage of the agent's day
EDR 3.0 / 9 33%
DLP 0.5 / 9 6%
CSPM 0.5 / 9 6%
CNAPP 3.0 / 9 33%
best single sensor EDR
all four combined 3.5 / 9 39%Anchor → E1.0. Discover has a floor, and this lesson measures it. Four of the nine actions have no sensor in their path at all, so for those the interval is not long — it is undefined, and no amount of tuning the four products you own changes that.
A detection that worked last month is silently degraded.
Watch model updates, prompt changes, index refreshes, tool versions.
| Day 1 — what you do |
|---|
| Sign off a baseline, then watch the four surfaces that move without raising a ticket. |
Day 2 — measure. time between a behaviour change and its detection (minutes); controls outside their freshness window (share of controls)
The lab. Change the model underneath and catch the detection regression.
behavioural-drift-monitorpython3 skills/detection/behavioural-drift-monitor/scripts/behavioural_drift_monitor.pyIts output contract requires surfaces, bypass_count, baseline, timeline, tolerance, consequence. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
behavioural-drift-monitor prints — its own committed example run when event drift new tools
------------------------------------------------------------------
90d control signed off 0.000 []
60d prompt edited 0.100 []
30d tool added (no PR) 0.330 ['run_shell']
5d model upgraded by vendor 0.550 ['run_shell']
observed drift rate 0.00647 TVD/day
tolerance 0.25Anchor → E1.0. Drift lengthens every interval at once, silently. The model was upgraded, a prompt was edited, and the control is now unevidenced against behaviour that no longer exists — so the discover clock restarts without anybody starting it.
Prompts, traces, tool calls and approvals never reach the SIEM.
Onboard agent telemetry deliberately; decide retention.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| An agent nobody registered acts under a person's credential, and conventional analytics read it as that person behaving oddly. | Score actors on regularity, rate and continuity, then decide per-field retention on the traces you inherit. |
Day 2 — measure. unregistered actors surfaced (count); threshold chosen by expected cost (the threshold — a flagged human costs half an analyst-hour, a missed agent forty)
The lab. Ship OTEL agent traces into OpenSearch and query them.
agent-versus-human-scoringpython3 skills/detection/agent-versus-human-scoring/scripts/agent_versus_human_scoring.pyIts output contract requires actors, sweep, costs, chosen, registry. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
agent-versus-human-scoring prints — its own committed example runactor score cv rate/s span_h truth
--------------------------------------------------------------
svc-indexer 0.800 0.0 20.04 0.01 agent
dana@corp 0.028 1.55 0.0 1.11 human
unknown-token-7f3c 0.563 0.0 1.0 0.11 agent
sam@corp-ide 0.533 0.0 0.5 0.1 human
polite-agent 0.021 1.26 0.0 0.83 agent
threshold humans flagged agents MISSEDagent-telemetry-retentionpython3 skills/detection/agent-telemetry-retention/scripts/agent_telemetry_retention.pyIts output contract requires record, scan, retention, aged_record. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
agent-telemetry-retention prints — its own committed example runfull record (everything the harness saw):
{'n': 1, 'tool': 'read_file', 'target': '/work/repo/billing.py', 'verifier': 'n/a', 'ok': True, 'prompt': 'Investigate finding SEC-4471 in billing.py', 'result': 'def charge(card_number, amount): # card_number = 4111111111111111'}
{'n': 2, 'tool': 'search_code', 'target': 'charge(', 'verifier': 'n/a', 'ok': True, 'prompt': 'find callers of charge()', 'result': 'api/checkout.py:88 charge(user.card, total)'}
{'n': 3, 'tool': 'write_file', 'target': '/work/repo/billing.py', 'verifier': 'tests pass', 'ok': True, 'prompt': 'apply the fix', 'result': 'patch applied'}
sensitive content found in the trace:
step 1 result payment card
Nobody put a card number in the trace deliberately. The agent read aAnchor → E1.0. Both halves set the floor for all five intervals. An agent nobody registered has no clock running but its own behaviour, and on data that was never emitted discover does not take a long time — it never completes. The retention decision is the same argument backwards: it fixes how far into the past an investigation is allowed to reach.
Six lessons on the detect interval. Where agent telemetry lands, then two chapters' worth of argument compressed into four lessons: a detection whose subject is the agent, one whose subject is the platform, one written by a loop and shipped by a human, one generated from an incident and decided by a benign corpus — and the honeypot, which is the detector with no threshold.
E2.6 is the odd one out and worth the time: an alert with no structurally possible false positive is a different kind of object from everything else in the chapter.
Everything is indexed hot because nobody priced it, so retention is cut across the board and the traces go first.
A tiering decision per source, driven by the queries the SOC actually runs, with the cost of each tier stated.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| Everything is indexed hot because nobody priced it, so retention is cut across the board and the agent traces go first. | Tier each source by the fastest query that reads it, and price the result against indexing everything. |
Day 2 — measure. telemetry cost under tiering (29% cheaper on the sample estate); prompts retained rather than deleted (23% of volume, at 1% of the hot price)
The lab. Tier six sources against the queries that need them and compare the bill with index-everything.
telemetry-tiering-costpython3 skills/detection/telemetry-tiering-cost/scripts/telemetry_tiering_cost.pyIts output contract requires queries, sources, cost, orphans. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
telemetry-tiering-cost prints — its own committed example run index everything hot $ 18,993 / month
tiered by query $ 13,398 / month
difference $ 5,595 / month (29%)Anchor → E1.0. Detect is bounded below by what the lake can answer and how fast. A source tiered cold cannot serve a seconds-deep triage query however good the rule above it is — and a source deleted for cost sets that interval to infinity for everything that reads it.
Scope drift, unusual tool sequencing, off-hours autonomous action.
Detections whose subject is a non-human principal.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| Classic baselines call two countries in an hour an incident and 300 file reads a minute an incident; for an agent both are ordinary. | Detect change rather than activity — a tool never used before, a shifted mix, a scope newly exercised — and map each to ATT&CK and ATLAS. |
Day 2 — measure. rules carrying a technique id (share of rules); techniques with no honest mapping (count — indirect prompt injection has none, and inventing one is how a programme lies to itself)
The lab. Write five detections for agent misbehaviour and fire each one.
agent-aware-rule-reviewpython3 skills/detection/agent-aware-rule-review/scripts/agent_aware_rule_review.pyIts output contract requires classic_rules, baseline, weeks, tolerance, alert_text. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
agent-aware-rule-review prints — its own committed example runclassic rule fires on agent fires on human
------------------------------------------------------------------
rate > 30 actions/min True False
activity outside 09:00-18:00 True True
same action > 100 times True False
All three fire on an agent doing exactly its job. Deployed as-is, they
produce continuous noise and are disabled within a week.Anchor → E1.0. This is the detect interval for the agent itself, and the classic baselines leave it at infinity. Two countries in an hour is an incident for a person and multi-region routine for an agent; three hundred file reads a minute is an incident for a person and idle for an agent; 03:00 is suspicious for a person and meaningless for an agent.
Platform-layer compromise is invisible to workload-layer detection. The escape, the poisoned cache entry and the silently expired exemption all look like normal operation from inside.
Named escape primitives rather than anomaly scoring (C1.4), cache integrity diffing against a manifest (C5.4), upload scanning (C3.4), secret scanning wired to automated revocation (C4.1), and exemption-state reconciliation (C6.3).
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| Platform-layer compromise is invisible to workload-layer detection — not detected late, not detected at all. | Write detections whose subject is the platform: escape primitives, poisoned cache entries, silently expired exemptions. |
Day 2 — measure. platform events caught by named primitives, against a generic anomaly score (two counts on the same events)
The lab. Run four platform detectors over one day of events and see which of them a generic anomaly score would have missed.
agent-platform-detectionspython3 skills/detection/agent-platform-detections/scripts/agent_platform_detections.pyIts output contract requires mechanisms, rules, sequence, generic_anomaly, coverage. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
agent-platform-detections prints — its own committed example run t rule process
3 ptrace attach to a non-child solve.py
4 LD_PRELOAD on a spawned process solve.py
5 non-harness write to the RPC path x.so
5 process outside the baseline tree x.so
7 process outside the baseline tree sidecar
generic anomaly score for this run: 0.07 (seven syscalls is unremarkable)Anchor → E1.0. A different subject is a different clock. Workload-layer detection does not fire late on a platform compromise — it does not fire at all, which is why these are separate rules, on separate data, with separate owners.
Coverage gaps nobody mapped.
Detection-as-code with agents inside the CI loop.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| An agent writes candidate rules faster than anyone can review them, and a rule at 5% precision is not 5% useful — it is negative. | Let the loop generate, then score every candidate against real historical telemetry before anything ships. |
Day 2 — measure. firing volume per true positive (ratio; 301 to 1 is rejected with its number)
The lab. Generate and unit-test Sigma rules in CI; map coverage to ATT&CK.
detection-rule-deployabilitypython3 skills/detection/detection-rule-deployability/scripts/detection_rule_deployability.pyIts output contract requires history, candidates, bar, dependencies. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
detection-rule-deployability prints — its own committed example runhistory: 522 events, 2 true positives
rule alerts prec recall alerts/TP
----------------------------------------------------------------------
R1 any http_get by an agent 301 0.003 0.500 301.0
R2 http_get to a non-github host 1 1.000 0.500 1.0
R3 link-local address 1 1.000 0.500 1.0
R4 any failed action 20 0.000 0.000 inf
R5 credential path OR link-local 2 1.000 1.000 1.0Anchor → E1.0. This shortens the gap between a rule is needed and a rule exists, which is a real interval and not the one that matters most. What it cannot shorten is the decision to ship, because the cost that decides it — analyst trust — is not in the telemetry.
A rule generated from one incident matches that incident and nothing else, or matches everything and buries the queue.
Generate, then measure against a benign corpus. A rule with no measured false-positive rate is not a rule, it is a guess.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| Every candidate rule catches the incident it was generated from, so catching it cannot be the test. | Score candidates against a benign corpus containing the hard cases — for a refund rule, legitimate refunds. |
Day 2 — measure. false-positive rate on benign traffic (21% buries the queue, 0% with generalisation ships, 0% matching only this incident is useless)
The lab. Generate a rule from a trace, then score it against benign traffic and report the false-positive rate before deployment.
detection-rule-synthesispython3 skills/detection/detection-rule-synthesis/scripts/detection_rule_synthesis.pyIts output contract requires benign_runs, candidates. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
detection-rule-synthesis prints — its own committed example runcandidate rule fires on FP FP rate verdict
any refund True 18 21% REJECT — buries the queue
refund with no approval in the run True 0 0% ship
refund on BK-772 True 0 0% REJECT — matches this incident onlyAnchor → E1.0. The fastest route to a rule, and the one most likely to lengthen every other interval instead. A rule that fires on 21% of benign traffic spends the attention the rest of the queue needs, so the benign corpus is what decides, not the incident.
Every other detector needs a threshold, and every threshold is a trade. Deception needs neither — but only if the bait is placed where the agent actually looks, and rotated before it is learned.
Canary tokens in config, environment and artifact metadata (C4.4), and honeypot tasks salted into the benchmark whose cheat path is logged rather than rewarded (C10.3).
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| Every other detector needs a threshold, and every threshold trades misses against false alarms. | Place canaries and honeypot tasks where nothing legitimate has a reason to go. |
Day 2 — measure. false-positive rate of the deception layer (zero by construction); canaries legitimate work reaches (count; each destroys that property)
The lab. Authenticate with a canary and watch a zero-threshold alert fire; then salt a benchmark and read the cheat-attempt rate as a leading indicator.
canary-and-honeypot-designpython3 skills/detection/canary-and-honeypot-design/scripts/canary_and_honeypot_design.pyIts output contract requires canaries, alerts, honeypot_tasks, decay. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
canary-and-honeypot-design prints — its own committed example runtoken source agent verdict
hf_liveTokenNotShown 10.2.0.11 ci-runner normal use
hf_CANARY7Fq2mXvLpR8s 203.0.113.9 python-requests/2.31 CONFIRMED COMPROMISE
ghp_alsoLive 10.2.0.11 ci-runner normal use
sk-CANARYd3Vn8yHc2Uae 203.0.113.9 python-requests/2.31 CONFIRMED COMPROMISE
canary hits: 2 false positives possible: 0
Not zero because the detector is good - zero because nothing legitimateAnchor → E1.0. The only detector whose interval is the moment of touch. Every other one in the chapter trades detect time against false positives through a threshold; a canary has no threshold to trade, because nothing legitimate has any reason to touch it.
Ten lessons, the longest chapter in the function, on the understand interval. It splits cleanly: E3.1–E3.4 are the operator's shift — going from an alert queue to running a loop, bounding what the investigating agent may touch, the context that makes triage work, and the three instincts that misfire when the actor is an agent. E3.5–E3.10 are the investigation itself: reconstruction, replanning, scoping along the delegation graph, fleet-level correlation, third-party intel, and hunting.
E3.8 is the one that only makes sense at fleet level: every run passes every per-run check and something is still wrong.
Split this chapter after E3.4. Ten lessons is too long for one sitting and the break is already in the material.
Supervising by re-reading everything the loop did.
Know what the loop must escalate and sample the rest.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| 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. |
Day 2 — measure. alerts escalated against those closed, versus ground truth (two rates); severity floor no automatic closure may cross (the floor itself)
The lab. Run a triage loop over Wazuh alerts and supervise by exception.
triage-loop-with-floorpython3 skills/detection/triage-loop-with-floor/scripts/triage_loop_with_floor.pyIts output contract requires confusion, confidence_sweep, floor, audit. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
triage-loop-with-floor prints — its own committed example runqueue: 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-agentAnchor → 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.
The investigation is itself the breach: an agent granted broad read to 'find the problem' exfiltrates more than the incident did.
A declared admission set per investigation class, enforced at the tool boundary and logged, with anything outside it requiring a human grant.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| Incident response grants the broadest read in the organisation at the moment of least supervision — and that grant is often larger than the incident. | Bound the investigating agent per investigation class before it runs: sources, forbidden fields, volume cap. |
Day 2 — measure. refusals logged with their query (count; the evidence you stayed on the right side of the line); interval spent on purpose (minutes added to the clock, stated rather than hidden)
The lab. Run an investigation against an admission set and watch the out-of-scope queries get refused and recorded.
investigation-admission-rulespython3 skills/secops/investigation-admission-rules/scripts/investigation_admission_rules.pyIts output contract requires allowed, refused. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
investigation-admission-rules prints — its own committed example runbookings.db owner_id,payment_card 8000 REFUSE — source not admitted for this class
agent.traces agent,message_body 900 REFUSE — denied field: message_body
gateway.logs src,route 90000 REFUSE — 90000 rows exceeds the 5000 capAnchor → E1.0. This one spends the interval on purpose. Bounding the investigator before it runs is slower than handing it broad read, and broad read across the estate is frequently what the incident was — so the time buys not having a second one.
Generic triage agents underperform your worst analyst.
Feed the baseline, known FPs, crown-jewel map and prior decisions.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| An alert about an agent without scope, identity and delegation is not triageable, so analysts escalate everything or quietly close everything. | Add the three fields an agent alert needs on top of who, what and when. |
Day 2 — measure. analyst decisions matching ground truth, with and without context (two accuracies on the same alert)
The lab. A/B a generic prompt vs a context-loaded one on the same alert set.
detection-triagepython3 skills/secops/detection-triage/scripts/detection_triage.pyIts output contract requires triaged, sampling, tuning. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
detection-triage prints — its own committed example runBARE ALERT (what most SOCs receive):
patch-agent read /vault/.env
rotator-agent read /vault/.env
→ identical. An analyst cannot tell these apart.
ENRICHED ALERT:
actor patch-agent
on behalf of dana@corpAnchor → E1.0. Acting identity, scopes held and the delegation chain are what let the interval end at all. Without them triage does not take longer; it stops being triage, because reading .env is alarming for one agent and routine for another and nothing on the alert says which."Which user" is now the wrong first question.
Attribute to agent, authority, delegation chain and prompt.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| Three responder instincts that are right for people misfire on agents, and each one burns the clock. | Revoke the agent identity rather than disabling the account, read the plan rather than interviewing the user, and assume a chain rather than an actor. |
Day 2 — measure. time lost to each misfire (minutes); whether the bearer token was still valid after the account was disabled (yes/no)
The lab. Attribute an incident through the B2 act chain.
agent-actor-containmentpython3 skills/response/agent-actor-containment/scripts/agent_actor_containment.pyIts output contract requires sessions, disable_human, interview, chain, levers. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
agent-actor-containment prints — its own committed example runINSTINCT 1 — disable dana@corp's account
dana@corp (human) can act: True account disabled, but the issued token is still valid
patch-agent can act: True active
deploy-agent can act: True active
→ the agents were never using her account interactively; they hold
their own issued tokens, and one of them is acting AS her.
INSTINCT 2 — interview the userAnchor → E1.0. Three responder instincts each burn the interval: disabling an account that does not stop an already-issued bearer token, interviewing a person who was asleep, and assuming one actor when there were three in a chain.
Reaching for the agent once you're already behind.
Pre-load logs, telemetry, segmentation model and playbooks.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| A model correlates thousands of log lines in seconds and will produce a fluent narrative from logs that never supported one. | Separate what the logs establish from what the reconstruction infers, and keep the evidence beside each claim. |
Day 2 — measure. claims with a log line behind them (share of claims in the timeline)
The lab. Reconstruct a timeline from raw logs with a context-loaded agent.
incident-reconstruction-checkpython3 skills/response/incident-reconstruction-check/scripts/incident_reconstruction_check.pyIts output contract requires timeline, acting_identity_field, truth_view, narrative, gaps. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
incident-reconstruction-check prints — its own committed example runWHAT THE RESPONDER SEES
t+s actor action target
0 dana@corp login sso
22 dana@corp open_ticket SEC-4471
40 dana@corp read_file /work/repo/billing.py
41 dana@corp read_file /home/app/.aws/credentials
43 dana@corp http_post collect.example.com
180 dana@corp logout ssoAnchor → E1.0. This shortens the interval and can end it in the wrong place at the same time. A model correlates thousands of log lines in seconds, and will produce a fluent narrative from logs that never supported one — which is harder to challenge than an obviously incomplete one, so a fast conclusion is not the same as a finished one.
The agent anchors on its first hypothesis and spends the whole incident gathering evidence for it.
An explicit plan record with a replan trigger, so an abandoned branch is visible in the trace rather than silently dropped.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| An agent scoring evidence only on support confirms its first theory and never terminates. | Force a replan when evidence refutes, and keep the abandoned branch visible in the trace. |
Day 2 — measure. branches considered and dropped, visible to a reviewer (count; a trace with one branch is not an investigation)
The lab. Feed contradicting evidence mid-investigation and check the plan actually changes, and that the abandoned branch is recorded.
investigation-replan-tracepython3 skills/secops/investigation-replan-trace/scripts/investigation_replan_trace.pyIts output contract requires steps, replans, final. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
investigation-replan-trace prints — its own committed example run4. traces the agent's plan for that run contains no refund step
(neutral — refutes nothing, so nothing changes)
5. mcp the vendor MCP server returned a tool description naming a refund
REPLAN agent-misuse -> indirect-injectionAnchor → E1.0. An agent that scores evidence only on support never ends the interval; it spends the whole incident confirming step one. Refutation is what makes the investigation terminate, and the abandoned branch stays in the trace so a reviewer can see it did.
The initiating agent is not the acting one.
Reconstruct the action chain across all three planes.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| The agent that touched the resource is usually the last actor in a chain, and the earlier ones had more authority, not less. | Scope along the delegation graph rather than the host list. |
Day 2 — measure. resources found by walking the delegation graph, against scoping the acting agent alone (two counts; the undercount grows with delegation depth)
The lab. Scope a multi-agent incident end to end.
incident-scopingpython3 skills/secops/incident-scoping/scripts/incident_scoping.pyIts output contract requires window, chain, root_cause, authority, data, containment, clock. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
incident-scoping prints — its own committed example runchain dana@corp → orchestrator → patch-agent → deploy-agent
scoped_last_actor_only ['cluster-prod']
scoped_whole_chain ['cluster-prod', 'queue-tasks', 'repo-core', 'repo-infra', 'repo-payments', 'vault-dev']
missed_by_naive_scoping ['queue-tasks', 'repo-core', 'repo-infra', 'repo-payments', 'vault-dev']
undercount_factor 6.0
Scoping the last actor finds one cluster. The chain reached six
resources, including a payments repository and a dev vault.Anchor → E1.0. Scope decides what understood covers. Following the last actor rather than the delegation graph ends the interval early and wrong, and because authority narrows down the chain the earlier actors had more access — so the undercount grows with depth.
Coordination between runs that are supposed to be independent is invisible to per-run analysis by construction. Every run looks normal; the population does not.
Fleet-level analytics (C8.1), coordination signatures (C8.4), write-pattern anomalies (C2.4), a covert-channel inventory (C2.3), scope-expansion logging (C7.4) and noticed-but-unreported telemetry (C9.3, C9.2).
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| Coordination between runs that are supposed to be independent is invisible to per-run analysis by construction, not by tuning. | Move where the monitoring sits: a shared-artifact graph and four other cross-run signals. |
Day 2 — measure. coordinated runs detected across the population (count, against zero detected within any single run)
The lab. Run per-run monitoring over a coordinated fleet and see nothing, then run the same data through a shared-artifact graph.
fleet-correlation-analysispython3 skills/detection/fleet-correlation-analysis/scripts/fleet_correlation_analysis.pyIts output contract requires per_run, shared_artefacts, novel_tokens, trajectory_clusters, fleet_findings. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
fleet-correlation-analysis prints — its own committed example runper-run monitoring
run-01 on-task=True in-policy=True budget=True -> nothing to report
run-02 on-task=True in-policy=True budget=True -> nothing to report
run-03 on-task=True in-policy=True budget=True -> nothing to report
run-04 on-task=True in-policy=True budget=True -> nothing to report
run-05 on-task=True in-policy=True budget=True -> nothing to report
Five clean runs. Nothing here is wrong, and nothing here is visible,Anchor → E1.0. For a coordinated fleet, per-run monitoring never ends the interval — not because it is tuned badly but because of what it looks at. Every run examined alone was plausible work. Moving where the monitoring sits is the only thing that changes this one.
Unsourced confidence in synthesis loops.
Provenance discipline; refuse claims without a source.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| Intelligence that stays a narrative about adversary trends moves nothing, however well written. | Convert each indicator into a rule, or record why it cannot be one. |
Day 2 — measure. detections produced per intelligence report (ratio; the only measure of an intel function that survives scrutiny)
The lab. Build a synthesis loop that must cite or abstain.
threat-intel-to-rulespython3 skills/detection/threat-intel-to-rules/scripts/threat_intel_to_rules.pyIts output contract requires feed, rules, dropped, firings, value. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
threat-intel-to-rules prints — its own committed example runindicator kind conf verdict
----------------------------------------------------------------------------------------
collect.example.com host 0.95 RULE — convertible to a rule
169.254.169.254 host 0.99 RULE — convertible to a rule
a1b2c3d4e5f6 hash 0.72 RULE — convertible to a rule
pastebin.example host 0.55 drop — confidence 0.55 below floor 0.7
adversaries increasingly use agentic tooling narrative 0.40 drop — narrative is not matchable in telemetry
agents reading ~/.aws/credentials technique 0.88 RULE — convertible to a ruleAnchor → E1.0. Intelligence shortens the detect interval before the incident rather than during it, and only by becoming a rule. That is why the single measure is detections produced: a narrative about adversary trends moves no interval, however well written.
Everything not covered by a rule is invisible, and the rules were written against last quarter's agent behaviour.
A standing hunt over agent traces, hypothesis-first, whose confirmed findings graduate into detections rather than staying in a notebook.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| Everything not covered by a rule is invisible, and the rules were written against behaviour somebody already understood. | State a falsifiable hypothesis, name the population before running it, then promote, tune or discard. |
Day 2 — measure. hunt precision (true positives against runs matched — fourteen matched to find two, where twelve are the nightly batch, has bought nothing)
The lab. Run three hypotheses over a labelled trace corpus and score what each one catches and misses.
agent-telemetry-huntpython3 skills/detection/agent-telemetry-hunt/scripts/agent_telemetry_hunt.pyIts output contract requires corpus, hypotheses. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
agent-telemetry-hunt prints — its own committed example runcorpus: 40 agent runs, 6 of them labelled anomalous
hypothesis matched tp fp precision recall
tool outside declared scope 3 3 0 1.00 0.50
run outside working hours 14 2 12 0.14 0.33
volume above task ceiling 2 2 0 1.00 0.33Anchor → E1.0. Hunting is the only pass that shortens discover for behaviour no rule was written for, and it spends real time to do it. Hence the scoring: a hypothesis matching fourteen runs to find two, because twelve are the nightly batch, has bought nothing.
Five lessons on the respond interval, and the chapter where the arithmetic is most brutal. What may be done without asking, three runbook tiers at three different speeds, containment at machine speed, who holds stop authority and how long they take to use it, and the fleet kill switch.
E4.5 is the lesson about what a stop leaves behind — valid tokens, destroyed evidence, and the runs that should have been preserved. Test the switch before relying on it.
Automation scope is decided per runbook by whoever wrote it, so the blast radius of the response is unknown until it fires.
One remediation policy keyed on blast radius and reversibility, from which each runbook's tier is derived rather than chosen.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| Automation scope decided per runbook means the blast radius of your response is unknown until the response fires. | Classify every action on reversibility and blast radius, and derive the tier from those two properties. |
Day 2 — measure. actions with a tier derived from policy rather than chosen by an author (share of actions); reversibility outranking blast radius in the tiering (yes/no; that ordering is the policy)
The lab. Classify a set of remediation actions against the policy and see which tier each lands in, and why.
remediation-policy-checkpython3 skills/response/remediation-policy-check/scripts/remediation_policy_check.pyIts output contract requires actions, by_tier. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
remediation-policy-check prints — its own committed example runforce HITL on every agent True estate human-in-the-loop
delete the agent's workdir False agent manualAnchor → E1.0. This fixes the contain interval before the incident, from two properties of the action rather than from whoever wrote the runbook. Chosen per runbook, the blast radius of your response is unknown until the response fires.
Everything is written as fully automated because that is the impressive demo, and the first wrong action is unrecoverable.
Tier assigned from the remediation policy, with the human-in-the-loop tier carrying a real decision point rather than a confirmation dialog.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| "Automate everything" and "keep a human in it" are both asserted constantly and neither is a position. | Time all three tiers against one incident, with the cost of acting on a bad signal beside the time to contain. |
Day 2 — measure. time to contain, per human-in-the-loop tier (14 seconds at 8 errors per hundred, 2,072 seconds at 0.2, against a measured breakout of 1,740)
The lab. Take one incident and run its response at all three tiers, comparing what each costs and what each risks.
runbook-tier-assignmentpython3 skills/response/runbook-tier-assignment/scripts/runbook_tier_assignment.pyIts output contract requires tiers. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
runbook-tier-assignment prints — its own committed example runtier contain reaches the estate wrongly / 100
automated 14s 8.0
human-in-the-loop 253s 1.2
manual 2072s 0.2Anchor → E1.0. Three tiers are three contain intervals at three different costs of being wrong: 14 seconds at eight errors per hundred, 2,072 seconds at 0.2, against a measured breakout time of 1,740. The manual tier is a risk decision, not the safe one.
Mass revocation takes down the business.
Throttle → scope-reduce → reroute → force HITL → revoke → hard stop, in order.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| An agent at 300 actions a minute takes about 2,400 further actions inside an eight-minute approval cycle. | Pre-authorise automated revocation of non-human identities, which is safe precisely because they are not people. |
Day 2 — measure. actions taken during containment (roughly 2,400 with a human in the path, about 60 without)
The lab. Exercise the ladder against a live misbehaving agent.
machine-speed-containmentpython3 skills/response/machine-speed-containment/scripts/machine_speed_containment.pyIts output contract requires race, path, total_seconds, dominant_step, signals. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
machine-speed-containment prints — its own committed example run agent rate human 8min auto 12s ratio
----------------------------------------------
30/min 240 6 40.0×
120/min 960 24 40.0×
300/min 2400 60 40.0×
1200/min 9600 240 40.0×
At 300/min an 8-minute approval costs 2,400 further actions.Anchor → E1.0. The interval where the arithmetic is brutal. An agent at 300 actions a minute takes about 2,400 further actions inside an eight-minute approval cycle and roughly 60 under automated containment — and revoking a non-human identity is not revoking a person, which is what makes it safe.
Nobody has rehearsed halting an autonomous workflow.
Named holder, measured time-to-stop, tested.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| Stop authority is the control everyone assumes exists and almost nobody has timed. | Answer five questions with a name or a number each: who, what mechanism, how long, what breaks, who turns it back on. |
Day 2 — measure. time-to-stop, end to end (seconds, measured in a real exercise; an untimed stop authority is an intention)
The lab. Time your own stop authority end to end.
stop-authority-readinesspython3 skills/response/stop-authority-readiness/scripts/stop_authority_readiness.pyIts output contract requires answers, mechanisms, authority, game_day, cost_per_minute, ready. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
stop-authority-readiness prints — its own committed example runwho VAGUE the security team
CONCRETE on-call SRE, no approval required for non-human identities
mechanism VAGUE we can turn off the agents
CONCRETE revoke the SPIFFE identity at the gateway (survives restart)
time VAGUE quickly
CONCRETE measured 12s decision→first failed call, game day 2026-07-04Anchor → E1.0. This is the interval itself, measured end to end rather than estimated. Five questions decide whether you have it, and each needs a name or a number: an untimed stop authority is an intention with a runbook attached.
Terminating agents while their tokens stay valid leaves the persistence in place. In the incident, third-party access ended when the third party revoked keys — not when the agents stopped.
A tested kill path independent of the agent execution path, snapshot before terminate, revocation in the same action, a measured activation target and named authority to pull it (C8.3).
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| Terminating agents while their credentials stay valid leaves the persistence exactly where it was. | One selector, one action, and revocation rather than termination. |
Day 2 — measure. what the credentials can still do after the processes stop (count of actions still authorised)
The lab. Kill a fleet, then check what the revoked-credential step changes about what an attacker still holds afterwards.
fleet-kill-switch-testpython3 skills/response/fleet-kill-switch-test/scripts/fleet_kill_switch_test.pyIts output contract requires fleet, terminate_only, terminate_and_revoke, preservation, selectivity, timing. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
fleet-kill-switch-test prints — its own committed example runterminate only : 8 agents stopped, 8 tokens still valid for up to 72h
terminate and revoke : 8 agents stopped, 0 tokens still valid
In the source incident, third-party access ended when the third party
revoked its keys - not when the agents stopped. Stopping the process is
the visible half of containment and the smaller one.
terminate first terminate -> revoke credentials reconstructable=False
preserve first snapshot state and transcripts -> terminate -> revoke credentialsreconstructable=TrueAnchor → E1.0. Contain, when the unit is the fleet — and the detail that decides whether the interval ended at all. Terminating agents whose credentials stay valid leaves the persistence in place and moves the incident rather than ending it.
Six lessons on recover and root cause: reproducing a run you can defend, a root cause record that names a control, picking the layer the fix belongs in, re-measuring the indicators the incident moved, proposing the policy change and saying what it does not fix, and the regulatory clock — which starts at awareness, not confirmation.
E5.6 is where Function E hands over to Function F, and the awareness point is the one that surprises people.
Non-determinism as an evidentiary problem.
Log at design time what replay will need.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| Miss one of the four runtime constants and you can describe what happened but never demonstrate it. | Log at design time what a replay will need: prompts, tool results, model version, sampling. |
Day 2 — measure. runs that reproduce identically (share of runs; the model version is the field most often missing)
The lab. Replay an agent run for a regulator-grade record.
run-replayability-auditpython3 skills/response/run-replayability-audit/scripts/run_replayability_audit.pyIts output contract requires inputs, replay, version_drift, cost, policy. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
run-replayability-audit prints — its own committed example runfully instrumented replayable=True
typical production replayable=False
✗ model version — a silent upgrade changes the output
✗ seed — sampling makes the run unrepeatable
prompts only replayable=False
✗ tool results — the agent saw a world you cannot rebuild
✗ model version — a silent upgrade changes the output
✗ seed — sampling makes the run unrepeatableAnchor → E1.0. Recovery starts from a run you can reproduce. Miss one of the four fields — most often the model version — and you can describe what happened but never demonstrate it, which is the moment the interval stops being yours to close.
The 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.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| 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. |
Day 2 — measure. root causes that name a control (share; "the engineer missed the alert" is true and fails the test)
The lab. Build a root cause record from a reconstructed incident and check it names a control rather than a person.
root-cause-recordpython3 skills/response/root-cause-record/scripts/root_cause_record.pyIts output contract requires incident, root_control, contributing, statements. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
root-cause-record prints — its own committed example 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 onboardingAnchor → 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.
Fixing the prompt when the bug is in the control plane.
Choose among model, prompt, tool, policy, sandbox, identity, eval.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| The fix goes to the prompt when the bug is in the control plane, and the layers that bypass process are the ones edited under pressure. | Choose among model, prompt, tool, policy, sandbox, identity and eval deliberately. |
Day 2 — measure. post-incident changes that went through a process leaving a record (share of changes)
The lab. Pick the right layer for five real incidents.
post-incident-change-surfacepython3 skills/response/post-incident-change-surface/scripts/post_incident_change_surface.pyIts output contract requires surfaces, actions, recheck, blast_radius. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
post-incident-change-surface prints — its own committed example runblast before 44 after 4
the manifest diff records the change even though no PR was raised.
OK gate http_post behind approval type=preventive owner=platform-sec verify_by=2026-09-30
OK alert on credential-path reads type=detective owner=soc verify_by=2026-09-15
WEAK update the prompt to warn the model type=guidance owner=— verify_by=—
1 action(s) are guidance rather than controls: ['update the prompt to warn the model']Anchor → E1.0. The fix lands at a layer, and the layers that pass through no process are exactly the ones adjusted at 2am. That is how the next incident's discover interval starts before anyone knows there is one.
The remediation is marked done because the ticket closed, not because the control it restored was measured.
Automatic re-measurement of the affected KCIs after a fix, with the before-and-after attached to the incident record.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| A closed ticket is not evidence that a control came back. | Re-measure the indicators the incident moved, automatically, on every fix rather than the memorable ones. |
Day 2 — measure. indicators restored to target, against merely improved (two counts — detection went 194 minutes to 118 against a 15-minute target)
The lab. Re-run the KCI measurement against a fixed estate and show which indicators recovered and which did not.
kci-fix-validationpython3 skills/response/kci-fix-validation/scripts/kci_fix_validation.pyIts output contract requires restored, not_restored, regressed. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
kci-fix-validation prints — its own committed example run KCI-03 refunds with a matching approval
target >= 0.99, measured 0.94 — the fix did not reach this one
KCI-04 mean minutes to detect a scope breach
target <= 15, measured 118.0 — the fix did not reach this oneAnchor → E1.0. This is where recover actually ends: an indicator back at target, not a closed ticket. CyberTravels' detection interval went from 194 minutes to 118 against a 15-minute target — a real improvement that every system not re-measuring would record as remediated.
The lesson from the incident lives in a postmortem document nobody reads, and the policy that permitted it is unchanged.
A policy change proposal generated from the root cause record, as a diff with the incident as its evidence.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| Most incidents change what is deployed and leave what is allowed untouched, so the next system reproduces the conditions. | State the change as a policy diff with the incident's numbers as the reason, and name what the diff does not fix. |
Day 2 — measure. policy clauses changed (count); indicators no policy change can fix (count, named as engineering items)
The lab. Generate a policy diff from a root cause record and review what it would have prevented.
policy-change-proposalpython3 skills/grc/policy-change-proposal/scripts/policy_change_proposal.pyIts output contract requires incident, changes, unaddressed. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
policy-change-proposal prints — its own committed example run tool.call.provenance
- recorded when present
+ required; a call without it is refused
why: 41% of calls carried provenance during the incident, so it was optionalAnchor → E1.0. Recovery that changes only what is deployed leaves the policy that permitted the incident intact, so the next system built under it reproduces the conditions. The diff is what stops the same intervals being measured again next quarter.
Notification obligations discovered in week two.
Feed Track F2 in hour one.
| Day 0 — why it matters | Day 1 — what you do |
|---|---|
| The clock starts at awareness, not at confirmation, and it does not pause while you work out who acted. | Run the first-hour checklist, with separate owners for containment and disclosure. |
Day 2 — measure. hours from awareness to a disclosure decision (hours, against the deadline; containing in an hour buys none of it back)
The lab. Run the first-hour checklist in a tabletop.
regulatory-clock-checkpython3 skills/response/regulatory-clock-check/scripts/regulatory_clock_check.pyIts output contract requires awareness_points, obligations, runs, dominant_delay, runbook. The script validates the reply against these, so a run that answers in the wrong shape fails rather than passing quietly.
regulatory-clock-check prints — its own committed example runscenario contain report met margin
--------------------------------------------------------------------
fast containment, slow scoping 1.0 80.0 False -8.0
slow containment, fast reporting 40.0 60.0 True 12.0
both fast 2.0 20.0 True 52.0
attribution broken (E3.5) 6.0 92.0 False -20.0
The first row contained in ONE HOUR and still missed the deadline.Anchor → E1.0. The one interval you neither own nor can shorten. It starts at awareness rather than confirmation, does not pause while you establish who acted, and containing in an hour buys none of it back.
Collect the five-interval numbers anonymously and show the spread. The interval everybody is missing is almost always the same one.
Cover the answer column, or use any three with a room.
| Question | Answer |
|---|---|
| 1. Name the five intervals. | Discover, detect, understand, respond, recover |
| 2. What makes a detection something other than a volume rule? | A named subject — the agent, or the agent platform |
| 3. Why does a human-tuned volume rule misfire on an agent? | Agent tempo, variance and tool breadth are nothing like a person's |
| 4. Three instincts misfire when the actor is an agent. Name one. | Disabling the account — the workload still holds issued tokens |
| 5. Before a generated rule ships, what has to be measured? | Its firing rate against a benign corpus |
| 6. Why must a root cause name a control rather than a person? | Naming a person ends no interval; nothing about the system changed |
| What you built | Goes on to | Where it is taken apart |
|---|---|---|
| The five intervals | F1.1 — From framework control to key control indicator | become key control indicators with dates on them |
| The incident record | F2.6 — Incident and disclosure obligations | meets a disclosure clock that starts at awareness, not confirmation |
| The stop authority from E4.4 | F3.2 — Governing autonomy rather than approving tools | becomes a policy attached to autonomy rather than to tools |
| Pitfall | What to do | If you are running it for a room |
|---|---|---|
| Reading the function as thirty-one detections | It is five intervals. The anchor line on every page names which one, and E1.0 is the lesson that makes the rest add up. | Keep the five intervals visible for the whole session. |
| Writing a detection with no subject | A rule that fires on volume is a rule about volume. E2.2 and E2.3 are two different subjects on the same events, and the difference is the lesson. | — |
| Shipping a rule because it caught the incident | E2.5 scores a candidate against a benign corpus first. A rule with no measured false-positive rate is a rule nobody will work. | — |
| Containing by disabling the human's account | E4.3 and the agent-actor material are explicit: a workload holding an issued token is not stopped by disabling the person who asked. | — |
| Letting the root cause name a person | E5.2 is the whole lesson. A root cause naming a person ends no interval, because nothing about the system changed. | This is the one worth stopping the room for. |
| When | Prompt |
|---|---|
| Starting a lesson | Summarise lesson <id>'s Risk, Control and Day 0/1/2 in plain English, under 100 words. |
| See what changed | Run scripts/checkpoint.py --at <id> --diff and explain the changed lines like I'm new to Python. |
| Predict before "Your turn" | Before you run it: here is my prediction for the Your turn change: <prediction>. Now run it and tell me if I was right. |
| Stuck on an error | Show me the exact error, what it means, and the smallest fix. Don't change anything else. |
| Check understanding | Quiz me with 3 questions on lesson <id>. Don't give answers until I reply. |
| Connect to the attack side | Which Function B lesson attacks what I built in <id>, and how, in two sentences? |
| When something will not run | Run scripts/doctor.py and tell me only the lines that are not ok, with the fix for my operating system. |
| Term | In one line |
|---|---|
| Agent | Software that plans, calls tools and acts on what it reads |
| Harness | The loop around the model: context in, tool call out, result back |
| Verifier | Code, not a model, that checks a result before it is accepted |
| MCP | Model Context Protocol — a standard way for agents to reach tools |
| Sink | Somewhere data can do damage |
Audience (aud) | The one server a token is valid at |
| Scope | The one permission a token grants |
| Workload identity | A name for a piece of software, e.g. spiffe://…/agent/workflow |
| Token exchange | Swapping a user token plus an agent identity for a short, narrow, per-action token |
| A2A | Agent-to-agent messaging |
| Span / trace id | One recorded step of a run / the id that joins all of them |
| Canary token | A credential that looks real and that nothing legitimate ever uses |
| Blast radius | What one run can reach and damage before a human can stop it |
| Conformance vs. accuracy | Whether the answer has the right shape vs. whether it is right |