mirror of
https://github.com/block/buzz.git
synced 2026-11-02 07:22:55 +00:00
docs(nips): add NIP-LE leader election draft
Documents the shared-identity multi-instance leader-election convention: the one-prompter invariant, the local-filesystem lock contract, and claim/hard-steal semantics. Lock is local-only and defines no wire format, so this touches no relay protocol. References NIP-OA, NIP-RS, and NIP-AO without amending them. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
parent
116486592d
commit
247e0a7127
114
docs/nips/NIP-LE.md
Normal file
114
docs/nips/NIP-LE.md
Normal file
@@ -0,0 +1,114 @@
|
||||
NIP-LE
|
||||
======
|
||||
|
||||
Leader Election (Shared-Identity Multi-Instance)
|
||||
------------------------------------------------
|
||||
|
||||
`draft` `optional`
|
||||
|
||||
This NIP defines a client-side, local-filesystem convention by which multiple
|
||||
instances running under a single shared agent identity elect exactly one
|
||||
*prompter* (leader), so that a mention fanned out to every instance produces a
|
||||
single agent response.
|
||||
|
||||
## Motivation
|
||||
|
||||
When several Buzz client instances run with the same agent keypair, the relay
|
||||
correctly fans every event addressed to that key out to all connected instances
|
||||
(per NIP-01). Without coordination, every instance promotes the event to an
|
||||
agent prompt and every instance responds — duplicating work and producing
|
||||
duplicate replies under one identity.
|
||||
|
||||
This commonly arises in development: a packaged build (DMG) and one or more
|
||||
`just staging` builds from in-progress worktrees may run concurrently, all
|
||||
sharing the developer's agent identities.
|
||||
|
||||
This NIP defines a minimal coordination layer that elects a single prompter per
|
||||
agent identity without any relay-side logic and without defining a wire format.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
This NIP does not define any Nostr event kind or tag.
|
||||
This NIP does not define relay-side coordination.
|
||||
This NIP does not define a cancel mechanism — hard-steal reuses NIP-AO's
|
||||
`cancel_turn` control frame.
|
||||
This NIP does not define the durable election identity source; it requires only
|
||||
that the identity be per-window-unique (see The Lock Contract).
|
||||
|
||||
## The Invariant
|
||||
|
||||
A single agent identity MAY have N subscribed instances but MUST have exactly
|
||||
one *prompter* (the leader).
|
||||
|
||||
Non-leader instances MUST subscribe to and render the full conversation — the
|
||||
message queue stays ungated so the UI displays all messages identically to the
|
||||
leader. Non-leader instances MUST suppress:
|
||||
|
||||
- (a) the prompt/dispatch path — they do not promote queued events to agent
|
||||
prompts; and
|
||||
- (b) pre-dispatch side-effects — specifically the `👀` acknowledgement reaction,
|
||||
which fires at queue-acceptance time, before dispatch.
|
||||
|
||||
Fail-safe: if no lock exists for an agent identity, every instance is a leader.
|
||||
This is the single-instance / solo-dev default and is byte-unchanged from
|
||||
behavior prior to this NIP — a solo developer has exactly one instance, which
|
||||
leads unconditionally.
|
||||
|
||||
## The Lock Contract
|
||||
|
||||
The lock lives on the LOCAL filesystem at
|
||||
`~/.buzz/leader-locks/<agent-pubkey-hex>.lock`. It is NOT a Nostr event and
|
||||
defines no wire format. This is why this NIP touches no relay protocol: leader
|
||||
election is entirely local to the machine running the instances.
|
||||
|
||||
The lock file contains a JSON object with the following shape
|
||||
(provisional — finalized against Phase 2 implementation):
|
||||
|
||||
```json
|
||||
{
|
||||
"instance_id": "<per-window-unique election id>",
|
||||
"pid": 12345,
|
||||
"claimed_at": "<iso8601 or unix timestamp>"
|
||||
}
|
||||
```
|
||||
|
||||
- `instance_id`: the identity of the leading window. It MUST be a
|
||||
per-window-unique election identity. The Tauri bundle identifier is
|
||||
insufficient — it collides across same-class windows (for example a DMG build
|
||||
and a `just staging` dev build, or two worktree builds whose unique-icon
|
||||
generation fell back to the shared identifier), causing two windows to both
|
||||
match the lock and both lead. A correct election identity is unique per
|
||||
running window (for example a process-unique value derived at launch).
|
||||
- `pid`: the operating-system process id of the leading window.
|
||||
- `claimed_at`: when the current leader claimed the lock.
|
||||
|
||||
An instance is the leader for an agent identity iff a lock file exists and its
|
||||
`instance_id` equals that instance's own election identity. A malformed or
|
||||
unparseable lock file MUST fail safe to leader, preserving solo-dev behavior.
|
||||
|
||||
Acquire and steal MUST be `flock`-guarded to close the read-check-write TOCTOU
|
||||
window between two instances racing to claim or steal the same lock.
|
||||
|
||||
## Claim/Steal Semantics
|
||||
|
||||
Ownership is explicit and sticky to a window: a window claims an agent (via the
|
||||
agent sidebar menu) and remains its leader until the lock is released or stolen.
|
||||
|
||||
Hard-steal: claiming an agent in window B immediately aborts window A's
|
||||
in-flight turn. The abort reuses NIP-AO's `cancel_turn` control frame
|
||||
(kind 24200); this NIP does not define a separate cancel mechanism. See NIP-AO
|
||||
for the control-frame structure and authorization rules.
|
||||
|
||||
## Relationship to other NIPs
|
||||
|
||||
NIP-LE references the following NIPs; it does not amend any of them.
|
||||
|
||||
- **NIP-OA (Owner Attestation)** — owns the owner↔agent identity model (one owner
|
||||
authorizes one agent key) but is silent on what happens when that same agent
|
||||
key runs in N instances at once. NIP-LE fills that gap.
|
||||
- **NIP-RS (Cross-Device Read State Sync)** — precedent for same-user,
|
||||
multi-instance coordination. NIP-RS synchronizes *data* (read position) across
|
||||
instances; NIP-LE coordinates *behavior* (which instance prompts).
|
||||
- **NIP-AO (Agent Observability)** — provides the `cancel_turn` control frame
|
||||
(kind 24200) that NIP-LE's hard-steal reuses to abort a displaced window's
|
||||
in-flight turn.
|
||||
Reference in New Issue
Block a user