ServicesRitual

Dead-Man Switches

Inverse mode turns the ritual primitive around: instead of firing when everyone acts together, the attempt fires when an assigned participant fails to check in on time. Same definitions, same attempts, same identity binding - one direction of time reversed.

Defining an inverse ritual

Set mode to inverse and give a checkin_interval_seconds; omitting the interval fails with 400 inverse rituals require checkin_interval_seconds. The escalation ladder lists what happens once the switch fires:

{
  "name": "On-Call Dead-Man Switch",
  "mode": "inverse",
  "parts": [
    { "part_id": "heartbeat", "assigned_to": "docs-wave" }
  ],
  "window_ms": 1,
  "checkin_interval_seconds": 4,
  "escalation": [
    { "after_missed_seconds": 0, "action": "notify", "pubsub_channel": "docs4-deadman-alerts" },
    { "after_missed_seconds": 60, "action": "fire", "webhook_ids": [] }
  ]
}

window_ms is required by the schema but plays no role in inverse mode; any positive value works.

The check-in loop

Arming sets the first deadline to checkin_interval_seconds from now, and each check-in pushes it out again:

snug ritual arm --def-id nQvtRMurcqXQtyGjajZc
snug ritual checkin --attempt-id UcFpuVdCMaDLsKYPAKfF --part heartbeat
{
  "attempt_id": "UcFpuVdCMaDLsKYPAKfF",
  "part_id": "heartbeat",
  "status": "armed",
  "next_deadline_ms": 1787926611219
}

Check-ins are identity-bound exactly like performs: a caller who is not the part's assigned_to gets 403 insufficient_permissions. perform on an inverse attempt is 400 mode_mismatch (inverse rituals use checkin, not perform), and checkin on a forward attempt is the mirror-image 400.

When the switch fires

Miss a deadline and the background scheduler (which sweeps every few seconds - see the CONFIG reference) marks the attempt fired and writes the usual report:

{
  "attempt_id": "UcFpuVdCMaDLsKYPAKfF",
  "status": "fired",
  "report": {
    "performed": 0,
    "required": 1,
    "missing_parts": ["heartbeat"],
    "spread_ms": 0,
    "verdict": "0 of 1 parts performed before the window closed"
  }
}

Firing is exactly once - an atomic latch claims the attempt before any side effects run. A check-in that races the firing loses cleanly: once the attempt has fired, further check-ins are 409 not_actionable (attempt is Fired). To resume coverage after a firing, arm a fresh attempt.

Escalation ladders

Each escalation step has an after_missed_seconds offset relative to the firing, an action (notify or fire), and its targets: a pubsub_channel and/or webhook_ids. Steps at offset 0 dispatch immediately when the switch fires; later steps are scheduled and dispatched by the same sweeper when their offset elapses, so a grace window before a destructive fire step is honored.

Captured live from the docs4-deadman-alerts channel at fire time:

{
  "attempt_id": "TSFTzmcWLFnJEscfMPrL",
  "ritual_def_id": "nQvtRMurcqXQtyGjajZc",
  "event": "escalation_notify",
  "after_missed_seconds": 0
}

A fire-action step publishes the same shape with "event": "escalation_fire". The action names intent - notify a human versus trigger the failover - but both are event dispatches; acting on them is the subscriber's job.

Rehearsal attempts (arm --rehearsal) follow the whole lifecycle, including firing, but suppress escalation dispatch.

History

Fired attempts land in the same history and analytics as forward attempts:

snug ritual history --def-id nQvtRMurcqXQtyGjajZc --status fired

The exact request and event schemas are in the Ritual API reference.

On this page