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.
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, oragent. 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 theplan_versionit 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), oradmin_invocation. At least one is required. - The succession rule picks the successor.
ordered_heirsresolves to its first heir.primogenitureandelectionare stored but do not resolve anyone yet - they fall straight through to therole_basedfallback, so give them one. - Dispositions are the will: an asset paired with a rule (
transfer,split,escrow, orburn). Treasury balances taketransferandsplit, Items taketransfer;escrow, plus thenameandroleassets, 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, andcase listare 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 listfiltered onsubject_kindandsubject_idreturns every version newest first, so the live plan is the highest version. Nothing stops you opening a case against a superseded one:case opentakes a plan id, and an old id pins that oldplan_version. - Lists take the shared list parameters. Plans filter on
subject_kind,subject_id,created_by,status,created_atandupdated_atand sort byversion; cases filter onplan_id,subject_kind,subject_id,opened_by,phase,created_atandupdated_atand sort byplan_version. Neither takesq. nameandroledispositions are declarative only. They validate and dry-run, but execution stops on them and leaves the casepartially_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
- Succession API - every endpoint, callable
- Related: Treasury and Item for the assets a will moves, Liveness for the inactivity signal, Human-in-the-Loop and Arbiter for adjudicating a challenge
