feat(a2a): cross-device pairing/trust store#84
Merged
Conversation
Foundation for cross-device peer messaging. The operator chose "circle key + first-contact pairing approval": a remote peer sharing the circle key still can't drop a message into this daemon's inbox until the receiving operator approves it once, after which the peer is trusted. PairingStore persists to ~/.config/clawtool/paired-peers.json and offers Observe (records first contact as pending with a 4-digit code; idempotent — never re-prompts an approved peer or mints a new code), IsApproved (the fast read for the inbound relay gate), Approve / Deny (by fingerprint or by the short code shown in the prompt), and List (newest contact first, for the pair CLI + app UI). Peers are keyed by an opaque fingerprint the relay layer supplies — peer_ids aren't stable across daemon restarts, so the wire side will present a stable per-install identity instead; this store never interprets it. Pure local state — the send-side LAN relay and the inbound gate that consults IsApproved land in the server + CLI layers next. Unit tests cover first-contact, idempotency, approve-by-fingerprint/code, deny + re-approve, unknown selector, persistence round-trip, and ordering.
bahadirarda
added a commit
that referenced
this pull request
May 25, 2026
Second of the cross-device messaging PRs (after the pairing store in #84). Adds the receive side of the LAN relay: the endpoint a remote daemon POSTs to, gated by the operator's chosen trust model. - POST /v1/relay (circle-aware, same circleOrBearer gate as /v1/agents): a paired peer relays a message here over the LAN. The handler runs the first-contact pairing gate — an unknown sender is recorded pending (202 + a 4-digit code) and refused until the receiving operator approves; an approved sender's message is delivered. Not a code-execution surface (it enqueues into local inboxes), so circle auth + pairing approval are the trust boundary. - a2a.InstallID / InstallDisplayName: a stable per-install fingerprint (persisted UUID) the relay will present as the sender identity — peer_ids churn per session and can't anchor a pairing decision. - Registry.DeliverToLocal: enqueues a relayed message into every LOCAL agent's inbox (source != mdns), so it reaches whatever agent is live here without looping back out to other remote peers. - a2a.GlobalPairingStore: lazy process-wide store the handler reads; corrupt-file-safe (re-prompts rather than wedging delivery). Tests: relay first-contact→202+code (nothing delivered), approve→200 delivered into the local inbox, missing-field rejects; InstallID stability; DeliverToLocal skips mdns peers. Send side next: `peer send` to an mdns target relays to the remote's /v1/relay (instead of writing the local inbox) and surfaces the pairing-required response.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First of three PRs wiring cross-device peer messaging (the operator's "namzu → DESKTOP mesaj gönderemiyor" report).
Why
Traced the gap:
clawtool peer sendPOSTs to the local daemon's inbox only — there's no LAN relay to the remote daemon, so a message to a remote peer never crosses the wire. (And MCPSendMessageis a local agent-CLI dispatcher, wrong layer for mDNS host peers.)The operator chose the trust model: circle key + first-contact pairing approval.
This PR (foundation)
a2a.PairingStore— daemon-local trust ledger, persisted to~/.config/clawtool/paired-peers.json:Observe(fingerprint, name, addr)— first contact → pending + 4-digit code; idempotent (never re-prompts an approved peer or mints a new code).IsApproved— fast read for the inbound relay gate.Approve/Deny— by fingerprint or by the short code shown in the prompt; deny is non-terminal.List— newest-first, for thepeer pairCLI + app approval UI.Pure local state. Peers keyed by an opaque fingerprint (peer_ids aren't stable across restarts; the relay layer will present a stable per-install identity).
Next
peer send→ remote daemon's inbox over LAN w/ circle key, reusing theproxyPeerCarddialer) + inbound gate onIsApproved.peer pair list/approve/denyCLI + app approval prompt + liveness re-announce loop.8 unit tests; full
internal/a2asuite green.