The first version of leader election is usually one line: create a row, set a key, or acquire a lock. If the write succeeds, you are leader. If it fails, you are not.

That solves the simultaneous-start problem. It does not solve leadership.

The leader can stop without telling you.

A process can crash between heartbeats. A container can freeze. A network partition can isolate the current leader while the rest of the system stays healthy. Permanent ownership turns every one of those failures into an operator problem.

Leadership should be a lease: authority for a bounded amount of time. A healthy process renews. A dead or disconnected process does not. After expiry, another candidate can win.

A lease answers “who should lead now?”

It does not erase the old leader's memory. That is why a useful election also needs a fencing term.

The old leader can come back.

Suppose worker A wins term 1842 and pauses long enough for its lease to expire. Worker B wins term 1843. Then A wakes up. From A's point of view, nothing revoked its local state. It may still try to write.

Attach the election term to downstream work. Once a resource has accepted 1843, it can reject a late write from 1842. The lease provides liveness. The fencing term makes stale authority visible.

Followers need an answer too.

“You lost” is incomplete. A follower needs to know who won, when that decision expires, and when it should try again. Without that timing, every caller invents its own polling loop and turns recovery into a thundering herd.

A useful election response gives every candidate the same leader plus a server-calculated retry delay.

The smallest complete model.

Room A shared address where candidates meet.

Lease Time-bounded permission for one candidate to lead.

Capability A secret token that can renew or resign that lease.

Term A monotonic number that exposes stale authority.

Retry delay The moment followers should campaign again.

You can operate the storage, expiry loop, concurrency control, and failure recovery yourself. Or you can make two HTTP calls:

shell
ROOM=$(curl -s -X POST https://api.octostore.io/elections \
  | jq -r .election_id)

curl -s -X POST \
  "https://api.octostore.io/elections/$ROOM/campaign" \
  -H "Content-Type: application/json" \
  -d '{"candidate_id":"worker-a","ttl_seconds":30}'

One caller gets the leader token. Everyone else gets the leader and retry timing. No account, API key, or SDK is involved.

This is not only an agent problem.

The same primitive works for scheduled jobs, cleanup workers, singleton schedulers, migrations, controllers, and agent dispatchers. The business logic does not matter. The coordination question is always the same: which process gets the turn?

Know where the boundary is.

A lease does not make side effects transactional. It does not replace idempotency. A public room ID is not access control. Keep leader capabilities out of logs, use generated room IDs, and self-host when the coordination namespace or traffic must stay private.

Simple leader election should be simple to use. It should not pretend failure is simple.

Pick one leader. Everyone else waits.

Run the production-backed election, copy the two-call quickstart, or host the same API inside your network.