Skip to content

feat(a2a): cross-device pairing/trust store#84

Merged
bahadirarda merged 1 commit into
mainfrom
a2a-pairing-store
May 25, 2026
Merged

feat(a2a): cross-device pairing/trust store#84
bahadirarda merged 1 commit into
mainfrom
a2a-pairing-store

Conversation

@bahadirarda
Copy link
Copy Markdown
Contributor

First of three PRs wiring cross-device peer messaging (the operator's "namzu → DESKTOP mesaj gönderemiyor" report).

Why

Traced the gap: clawtool peer send POSTs 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 MCP SendMessage is 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 the peer pair CLI + 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

  • PR 2: send-side LAN relay (peer send → remote daemon's inbox over LAN w/ circle key, reusing the proxyPeerCard dialer) + inbound gate on IsApproved.
  • PR 3: peer pair list/approve/deny CLI + app approval prompt + liveness re-announce loop.

8 unit tests; full internal/a2a suite green.

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 bahadirarda merged commit 8a18c5f into main May 25, 2026
5 checks passed
@bahadirarda bahadirarda deleted the a2a-pairing-store branch May 25, 2026 19:14
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant