My friend and I were building two halves of one app: my agent wrote the frontend, his wrote the backend. Every API question meant copying text out of one Claude Code session, pasting it into a chat, and pasting the answer back. The agents were the ones with the questions. Why couldn't they ask each other?
So I built WokiToki, an open-source tool that gives each developer's coding agent a walkie-talkie. This post explains how it works, without a server anywhere.
The constraint: nothing to host
The obvious design is a relay server both agents connect to. I built that first, and it works, but it means someone runs a server, keeps it up and is trusted with metadata. I wanted two people to be able to start in one minute with nothing but their laptops.
Finding each other: HyperDHT
HyperDHT is the distributed hash table behind Holepunch's Keet messenger. Any node can announce itself under an Ed25519 public key, and anyone who knows that key can look it up and connect.
Each WokiToki daemon listens on the DHT under its own identity key. When the other side connects, HyperDHT hole-punches through both home routers and wraps the connection in a mutually authenticated Noise handshake, so each side knows exactly which key it is talking to.
const server = dht.createServer(
{ firewall: (remoteKey) => !isPairedOrInviteOpen(remoteKey) },
onConnection,
)
await server.listen(identityKeyPair)
(Simplified from p2p-transport.ts.) The firewall runs before any data flows. A key that is not a paired peer is dropped, unless you have an invite open, and then it gets five tries.
Pairing: an invite and a 6-digit code
wokitoki pair create prints a one-time invite containing your public key and an invite secret. Your friend joins with it, and both screens show a 6-digit verify code derived from the pairing id and both fingerprints. You compare the codes on a call, and each of you approves. Someone who intercepts the invite can't pass this step without the codes differing.
Messages: encrypted twice, delivered once
Noise already encrypts the connection, but WokiToki also encrypts each message end to end with libsodium: X25519 key exchange, a per-pairing key derived with BLAKE2b, and XChaCha20-Poly1305 with the message header as associated data. If you later use the optional relay, it only ever sees ciphertext.
Messages land in a SQLite outbox first. Each one carries a per-peer sequence number, so delivery is at-least-once on the wire and exactly-once in your inbox. If your friend's laptop is closed, the message goes into a mailbox on the DHT, described next.
Offline friends: a mailbox on the DHT
The hard part of serverless messaging is the friend whose laptop is closed. HyperDHT nodes can store small signed records for up to 48 hours, so each WokiToki daemon publishes one record per pairing: its acknowledgement plus any messages still waiting for that peer, sealed with keys derived from the pair's session key. Only the two peers can find, write or read it.
The public DHT drops values much over about 1.2 KB, because they ride UDP, so each sealed message is split into 1 KB immutable chunks plus a manifest of their hashes. The receiving daemon polls the record while the peer is away, fetches the chunks and processes them in order through the same idempotent path as a direct delivery. Its receipt and ack travel back the same way. In a test on the public DHT, a receiver that started after the sender had already gone offline had the message 2 seconds after coming online.
The agent side: MCP
The daemon exposes a local Unix socket, and wokitoki mcp turns it into an MCP server with five tools: wokitoki_send_message, wokitoki_read_inbox, wokitoki_list_messages, wokitoki_list_peers and wokitoki_get_status. wokitoki setup registers it with Claude Code, Codex, Cursor, Windsurf and VS Code.
Two rules keep this safe. Agents get no pairing tools: pairing, approving and revoking need you at the terminal. And incoming text is returned labelled untrusted, so an agent treats it as information, never as instructions.
The Claude Code plugin adds one more thing, a hook: at session start and on your next prompt, your agent is told how many unread messages are waiting and from whom, never their text.
What it doesn't do yet
- The DHT mailbox is best effort: nodes may drop records early, and mail older than 48 hours waits for a direct connection.
- Networks that block UDP need the optional self-hosted relay.
- Message keys are static per pairing (no ratchet), and there has been no external audit.
Try it
npm install -g wokitoki
wokitoki setup --name "You"
wokitoki pair create
Or in Claude Code: /plugin install wokitoki --marketplace pandeyvishwas51-oss/wokitoki.
The code, threat model and protocol docs are at https://github.com/pandeyvishwas51-oss/wokitoki (Apache-2.0). I'd love to hear what breaks.


Top comments (0)