Skip to content

Succession

Try the interactive example ↓

What happens to a subject's assets when it dies, leaves, or goes dark. A plan declares the triggers that may open a case, the rule that picks a successor, and the per-asset dispositions that make up the will. A case is one triggered run of that plan, moving through grace, contestation, and execution to a signed receipt.

When to reach for it: guild ownership passing when the owner goes inactive, permadeath wills, digital inheritance for accounts and collections, retiring an agent onto a successor, ops credential handover when an admin goes dark.

When not to: moving one balance or one item to a new owner right now is Treasury and Item; noticing that something stopped reporting is Liveness; a single human approval on a single decision is Human-in-the-Loop.

Concepts

  • Subject - who the plan is about: auth_user, guild, entity, or agent. It travels as a {kind, id} object in request bodies and as the string {kind}:{id} in query parameters and CLI flags.
  • Plans are versioned, never edited. There is no update or delete endpoint; creating a plan for a subject that already has one stores a new one at version + 1. Prior versions stay readable, and a case pins the plan_version it opened under.
  • Triggers declare what may open a case: explicit_event (a list of domain event names), inactivity (a day threshold, optionally sourced from Liveness), or admin_invocation. At least one is required.
  • The succession rule picks the successor. ordered_heirs resolves to its first heir. primogeniture and election are stored but do not resolve anyone yet - they fall straight through to the role_based fallback, so give them one.
  • Dispositions are the will: an asset paired with a rule (transfer, split, escrow, or burn). Treasury balances take transfer and split, Items take transfer; escrow, plus the name and role assets, are accepted but deferred.
  • Safeguards guard against false positives: a grace period, an optional dead-man-switch confirmation ladder, and a contestation window.
  • A subject has at most one open case. Opening a second is 409 case_already_open; the slot frees when the case stands down, is voided, or fully executes.

Plan, case and challenge IDs are generated as UUIDv7. Use the ID returned by creation or opening when referring to that resource.

Author a plan

The plan body - triggers, rule, dispositions, safeguards - goes in a JSON file; the CLI takes the subject separately.

cat > will.json <<'JSON'
{
  "triggers": [ { "kind": "inactivity", "source": "liveness", "threshold_days": 90 },
                { "kind": "admin_invocation" } ],
  "succession_rule": {
    "kind": "ordered_heirs",
    "heirs": [ { "kind": "auth_user", "id": "heir-primary" },
               { "kind": "auth_user", "id": "heir-backup" } ],
    "fallback": { "kind": "role_based", "role": "guild_officer", "selector": "most_senior" }
  },
  "dispositions": [
    { "asset": { "kind": "treasury_balance", "domain": "estate", "currency_id": "gold" },
      "rule": { "kind": "split", "shares": [ { "heir_index": 0, "fraction": 0.6 },
                                             { "heir_index": 1, "fraction": 0.4 } ] } } ],
  "safeguards": { "grace_period_days": 7, "contestation_window_days": 14,
                  "challenge_resolution": ["hitl"] }
}
JSON

snug succession plan create --subject auth_user:usr_9f2c --file will.json
snug succession plan get --id 01a049b5-e6c8-77b7-a541-d34c0a9084d5
snug succession plan list --filter 'subject_kind[eq]=auth_user' --filter 'subject_id[eq]=usr_9f2c'

Over HTTP the subject travels in the same body:

curl -X POST -H "Authorization: Bearer $SNUG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"subject":{"kind":"guild","id":"ashen-keep"},
       "triggers":[{"kind":"admin_invocation"}],
       "succession_rule":{"kind":"ordered_heirs",
         "heirs":[{"kind":"auth_user","id":"heir-primary"}]},
       "safeguards":{"grace_period_days":7,"contestation_window_days":14}}' \
  http://localhost:4000/api/v1/succession/plans
{
  "status": 201,
  "msg": "Created",
  "data": {
    "plan_id": "01a049b5-e6c8-7451-a348-54e07465bfdd",
    "subject": { "kind": "guild", "id": "ashen-keep" },
    "created_by": "docs-wave-admin",
    "version": 1,
    "triggers": [ { "kind": "admin_invocation" } ],
    "succession_rule": { "kind": "ordered_heirs", "heirs": [ ... ], "fallback": null },
    "dispositions": [],
    "safeguards": { "grace_period_days": 7, "dead_man_switch": null,
                    "contestation_window_days": 14, "challenge_resolution": [] },
    "status": "active",
    "created_at": "2026-08-28T18:50:09.932619Z", "updated_at": "..."
  }
}

Who may write a plan: the subject itself, when the subject is auth_user: your own principal, or a platform admin. Anything else - another user's plan, or any guild, entity, or agent subject - is 403 insufficient_permissions, so plans for non-user subjects need a platform admin token.

Validation runs at create time, not execution time: an empty triggers list is 400 invalid_trigger; ordered_heirs with neither heirs nor a fallback, a dead_man_switch with interval_days: 0, and a window past the 365-day cap are all 400 invalid_plan; split fractions that do not sum to 1.0 are 400 invalid_disposition; overrunning a count cap is 400 limit_exceeded.

Preview before you trust it

A dry run resolves the successor and renders the would-be transfer manifest against live state, changing nothing:

snug succession plan dry-run --id 01a049b5-e6c8-77b7-a541-d34c0a9084d5
snug succession plan dry-run --id 01a049b5-e6c8-77b7-a541-d34c0a9084d5 \
  --assume-successor auth_user:heir-backup
{
  "plan_id": "01a049b5-e6c8-77b7-a541-d34c0a9084d5",
  "plan_version": 6,
  "resolved_successor": { "kind": "auth_user", "id": "heir-primary" },
  "legs": [
    { "asset": { "kind": "treasury_balance", "domain": "estate", "currency_id": "gold" },
      "rule": { "kind": "transfer", "to": null },
      "beneficiary": "auth_user:heir-primary" }
  ],
  "warnings": [ "Role disposition (guild_owner) is not yet executable (deferred)" ]
}

warnings is where the value is: frozen or insolvent source wallets, items the subject no longer holds, split shares pointing at a heir index that does not exist, and dispositions that cannot execute at all. An empty warnings list is the only real go-ahead. --assume-successor substitutes a successor so you can price a hypothetical outcome.

A rule that resolves nobody returns "resolved_successor": null plus the warning "No successor could be resolved under the current rule". With a role_based fallback it resolves instead to the placeholder auth_user:role:{role} - a literal string, not a lookup of who holds the role today.

Run a case

Opening, phases, standing down, contestation, and execution have their own page: Cases and execution.

Behaviors and gotchas

  • Reads are tenant-wide. plan get, plan list, case get, and case list are not scoped to the caller - any authenticated token in the tenant reads every plan and case. Only writes check subject authority. Verified with a second token.
  • Version is per subject, not per plan. plan list filtered on subject_kind and subject_id returns every version newest first, so the live plan is the highest version. Nothing stops you opening a case against a superseded one: case open takes a plan id, and an old id pins that old plan_version.
  • Lists take the shared list parameters. Plans filter on subject_kind, subject_id, created_by, status, created_at and updated_at and sort by version; cases filter on plan_id, subject_kind, subject_id, opened_by, phase, created_at and updated_at and sort by plan_version. Neither takes q.
  • name and role dispositions are declarative only. They validate and dry-run, but execution stops on them and leaves the case partially_executed. Keep them out of a will you mean to execute.

Limits and configuration

A plan may carry 16 triggers, 64 heirs, and 100 dispositions, with grace and contestation windows capped at 365 days each. Those bounds, the lifecycle sweeper, the dead-man-switch clock, and the burn-sink wallet are set by the SUCCESSION_* variables.

Reference

Interactive example

Plan who takes over when an owner is absent or a trigger occurs. Use a grace period and a recorded handover to make the transition deliberate.

An illustrative example that runs in your browser. No account or API calls required.