ServicesSuccession

Succession

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. Every example on this page was executed against a live server.

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.

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 plan_320c87c3eb2a47c09ef8649eb9e7baa9
snug succession plan list --subject auth_user: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": "plan_128cc98830c243ce897e600297d924ec",
    "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 plan_320c87c3eb2a47c09ef8649eb9e7baa9
snug succession plan dry-run --id plan_320c87c3eb2a47c09ef8649eb9e7baa9 \
  --assume-successor auth_user:heir-backup
{
  "plan_id": "plan_320c87c3eb2a47c09ef8649eb9e7baa9",
  "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 --subject 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.
  • List filters are fixed, not the shared search grammar. These endpoints take subject, phase (cases only), page, page_size, sort_by, and sort_order - no q, no filter. The CLI exposes only --subject, --phase, --page, and --page-size; use HTTP to sort.
  • 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 in the Succession CONFIG reference.

Reference

On this page