Skip to content

Extract a shared Elixir lease client — two independently-designed reclaim mechanisms now exist in the fleet #1462

Description

@cirwel

Survey (2026-08-01, all verified against live code): the BEAM fleet has four independent Elixir lease-plane clients, and two of them have independently designed different orphan-reclaim mechanisms with different authorship proofs and different blind spots, neither aware of the other:

Client Lost-response recovery Restart-orphan recovery Authorship proof
unitares/elixir/sentinel LeaseAdvisory+LeaseReclaim (#1459) idempotent retry + uuid-history reclaim none (documented residual: in-memory candidates die on restart) per-attempt crypto-random uuid history — strong, restart-fragile
dispatch_beam Dispatch.Lease per-PID stable holder uuid (self-heals via server idempotency; safe only because the daemon is a singleton) intent-tag reclaim (@owner_intent "dispatch_beam_holder" stamped on every acquire; on 409, reclaim iff the blocking lease carries the tag) intent string — restart-proof, but weaker (any process could stamp it)
unitares/elixir/agent_orchestrator LeasePlaneClient none (bare single-shot :httpc) none n/a (agent:/ surfaces, not the auto-renew class)
unitares/elixir/lease_plane outbound clients n/a (not lease acquirers)

The two reclaim designs are complementary: dispatch_beam's intent-tag mechanism is exactly the answer to #1459's documented restart-forfeiture residual, and #1459's uuid-history + bounded idempotent retry is stronger where concurrent instances exist (per-PID stable uuids were explicitly rejected for sentinel as a double-grant risk — but they're valid for a singleton). Subtle safety design is being done twice, divergently — the expensive kind of duplication, and the same hand-copy drift class that gave both sentinel GenServers an identical starvation blind spot in July.

Proposal:

  1. Extract a shared unitares_lease_client library (transport + idempotent retry + response classification + uuid-history reclaim from fix(sentinel): reclaim own leases stranded by lost acquire/release responses #1459, plus an opt-in intent-tag reclaim mode absorbed from Dispatch.Lease for singleton daemons / restart survival).
  2. In-repo consumers (sentinel, agent_orchestrator) take it as a path dep; dispatch_beam (separate repo) via a mix git dep with sparse checkout, or hex if a third external consumer appears.
  3. Sequence post-Aug-8. Do Port lost-response lease recovery + own-orphan reclaim to the Python SDK advisory client #1460 (Python parity — the surfaces demonstrably orphaning today) first.

Design inputs that must not be re-derived: the #1459 council conclusions (retention semantics — never age-alone, never outcome-clearing; 422-only 'normal' fallback), the rejected server-side renew cap, and dispatch_beam's moduledoc ("Surviving a daemon restart", "no client heartbeat needed" — verified against plane source).

https://claude.ai/code/session_01HVjiaUeuBrcA2oyRaLWUQA

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions