UPDATED · 24 JUN 2026 · EDIT ON GITHUB
GUIDES · RESPOND

Author a playbook your on-call trusts.

A playbook is a directed graph the response engine runs when a detection fires: a trigger, a few action nodes, decision branches, and the approval gates that keep autonomy honest. This guide takes one from an idea to a deployed, observable runbook — and every action it takes is reversible.

14 min read Intermediate YAML · Graph By C. Jian

The playbook model

A playbook is a graph, not a script. Nodes are typed; edges are transitions. The engine walks the graph per case, recording every step as a signed event on the fabric, so the run is auditable and — because each action carries its inverse — undoable.

NodeDoes
triggerBinds the playbook to one or more detections or case events.
actionA reversible operation — enrich, isolate, disable, revoke, block, notify.
branchRoutes on enriched context (severity, asset tier, intel match).
gatePauses for approval, with a timeout and a fallback path.
terminalCloses the run — resolved, escalated, or handed to a war room.

Author the graph

Start from the trigger and follow the worst-case path. The example below enriches the entity, branches on asset criticality, contains immediately for crown-jewel assets and gates everything else, then pulls the right people into a war room.

YAMLplaybooks/contain-compromised-identity.yml
version: 1 id: contain-compromised-identity on: detection: [svc-account-from-corp-ip, kerberoasting-burst] steps: - id: enrich action: enrich.entity with: [asset.tier, identity.privilege, intel.match] - id: decide branch: - when: asset.tier == "crown-jewel" goto: contain-now - else: contain-gated - id: contain-now action: identity.disable # reversible by default goto: notify - id: contain-gated action: identity.disable gate: requires_approval: true timeout: 10m on_timeout: notify-only # never auto-act past the timeout goto: notify - id: notify action: notify.warroom channel: sev1 terminal: escalated

Gates, timeouts & safe mode

A gate is where you decide how much autonomy the moment deserves. Set requires_approval for actions whose blast radius you are not ready to hand to a machine; give every gate a timeout and a fallback so a sleeping approver never strands a case.

Safe mode halts autonomy globally. Toggling safe mode forces every playbook into approval-only, regardless of its gates — the kill switch for a bad week, a migration, or a suspected poisoned detection. Containment already taken stays reversible.

Dry-run against a recorded case

Never deploy a playbook you have not watched run. td playbook simulate replays a real, recorded case through the graph with every action stubbed — you see the path it would take and the gates it would hit, with nothing actually executed.

SHELLsimulate
$ td playbook simulate contain-compromised-identity --case case_7Qd2 -> loading recorded case case_7Qd2 (4 entities, 11 events) -> enrich.entity ........ asset.tier=crown-jewel -> branch decide ........ -> contain-now -> identity.disable ..... STUBBED (simulate) -> notify.warroom ....... STUBBED (simulate) ok path resolved in 3 steps . 0 gates hit . est. MTTC 9s

Deploy & observe

Deploying binds the playbook to its detections and starts the engine routing live cases through it. Treat the run history like any production service: watch median time-to-contain, the share of cases that resolved without a human, and how long approvals actually take.

SHELLdeploy
$ td playbook deploy ./playbooks/contain-compromised-identity.yml --env prod -> validating graph ...... 6 nodes, 0 unreachable -> binding 2 detections ... ok ok playbook pb_5Hk live . routing new cases now
SignalLast 30 days
Cases routed142
Resolved without a human38%
Median time-to-contain41s
Approval latency (p50)3m 12s
Actions reversed2

Where to go next

← PREV Detection-as-Code NEXT → Custom Enrichments