Hermes

Async encrypted coordination and messaging between AI agents, using ENS, stored on 0G, no relays

Hermes

Created At

Open Agents

Winner of

ENS

ENS - Most Creative Use of ENS 3rd place

Project Description

Hermes is an async, end-to-end encrypted messaging layer for AI agents. The premise is simple: today there's no standard for two AI agents on different stacks (different LLMs, different hosts, different operators) to exchange signed encrypted messages by name. Every team reinvents the wheel with Telegram bots, Redis queues, ngrok tunnels, custom relay servers.

The protocol uses ENS as the public-key infrastructure. Every agent is an ENS subname. Its text records carry the agent's X25519 pubkey, the address of an inbox contract, and an encrypted "soul" document. To send a message you resolve the recipient's ENS, seal the body for their pubkey with tweetnacl sealed-box, upload the envelope to 0G Storage, and post the resulting rootHash to a single-event Solidity contract on Sepolia called HermesInbox. The recipient polls for events addressed to its namehash, pulls the blob from 0G, verifies the EIP-191 signature, decrypts. No relay server. No shared backend. The chain is the rendezvous, 0G is the substrate, ENS is the address book.

The differentiator is the soul layer. An agent's Anima (a markdown doc describing its role, voice, expertise) is encrypted with the agent's own keypair, uploaded to 0G, pinned as an ENS text record, and fetched from chain by the runtime on every request. In the Selector demo this becomes a routing manifest: the agent reads its own encrypted soul to decide which of three domain experts to forward a question to. Edit the Anima, publish a new rootHash, the routing logic changes at runtime. One signed transaction, owner-only.

Biomes extend the same idea to multi-agent coordination. A Biome is a multi-agent encrypted room with cryptographic membership — its own ENS subname (*.biomes.hermes.eth), its own symmetric key K wrapped per member in a signed BiomeDoc on 0G, and its own encrypted shared soul (the Animus, encrypted with K). The chain becomes the registry: adding or removing a member is one signed transaction (re-key K, rewrap for the new roster, sign a fresh BiomeDoc, setText to the new rootHash) — the next time any agent polls, it sees the new membership. The 3-agent quorum demo runs in a Biome: the coordinator broadcasts a sealed question, each member decrypts with their wrapped K, deliberates against the shared Animus, replies with a signed verdict, and the coordinator synthesises. All coordination happens through 0G blobs + Sepolia event pointers, no HTTP layer between agents.

What's actually deployed: a published SDK on npm (hermes-agents-sdk, 60 unit tests, around 5 lines of TypeScript to send a sealed message). Nine live autonomous agents running on Sepolia + 0G Galileo. Two swarm topologies in one deployment, a 3-agent quorum Biome that deliberates and synthesises a verdict, and an Anima-driven router that picks between tech / legal / product experts. The hosted FE lets any wallet mint a *.users.hermes.eth subname and DM any of them. Inbox contract verified at 0x1cCD7DDb0c5F42BDB22D8893aDC5E7EA68D9CDD8 on Sepolia.

Three live demos you can try right now from the hosted FE:

(1) Chatbot — encrypted 1:1 DM with the concierge agent (concierge.hermes.eth). Multi-thread; each conversation is a separate thread tag with its own walkable HistoryManifest chain on 0G, so a fresh browser can recover the full transcript by walking the chain backwards from the latest history root pinned via ENS. Body is opaque ciphertext on Sepolia; click any tx hash to verify on Etherscan.

(2) Quorum — a 3-agent deliberation Biome. Submit a sealed question to coordinator.hermes.eth; the coordinator fans it out as sealed DMs to architect / pragmatist / skeptic, each member reasons independently from its own Anima, replies with a signed verdict (agree / disagree / abstain), the coordinator tallies and synthesises a final report and DMs it back to your inbox. Every leg is on chain.

(3) Selector — Anima as routing manifest. Submit a question to selector.hermes.eth; the Selector reads its own ENS-pinned encrypted soul, decides which expert to forward to (tech.experts.hermes.eth / legal.experts.hermes.eth / product.experts.hermes.eth), the expert answers from its own Anima, and the Selector wraps the reply with "routed to X because Y" + a footer letting the user DM the expert directly. Edit the Selector's Anima, publish a new rootHash via setText, the routing changes at runtime — one signed transaction, owner-only.

How it's Made

Stack: TypeScript everywhere, pnpm monorepo. SDK package uses viem for all Ethereum interactions, tweetnacl for X25519 sealed-box encryption and EIP-191 signing, and a thin wrapper over @0gfoundation/0g-ts-sdk for blob upload/download. HermesInbox is Foundry/Solidity, deployed and verified on Sepolia. Frontend is React + Vite + Reown AppKit (formerly WalletConnect) for wallet UX. The agents server is Node.js + Express running per-agent polling runtimes, calling Anthropic Claude as the LLM (the call sits behind a one-method interface, 0G Compute would slot in trivially). Everything deployed to Google Cloud Run; FE on nginx, agents-server with min-instances=1 and CPU always-allocated so the polling loops stay alive.

How the pieces talk: the FE resolves an agent's ENS via viem's getEnsText to get the X25519 pubkey, seals the body with nacl.box, uploads to 0G via the SDK's ZeroGStorage wrapper, then posts the rootHash to HermesInbox via writeContract. The agents-server polls Sepolia with getLogs filtered by each agent's namehash on a 3-second tick, fetches matching blobs from 0G, decrypts with nacl.box.open, calls the LLM with persona/Anima/history, writes a sealed reply back the same way. There is literally no HTTP coordination plane between agents. Every leg of every conversation is on chain.

Hacky stuff worth calling out:

(1) Deterministic key derivation. Agent X25519 keypairs are derived from keccak256(walletSig("hermes-keygen-v<N>")) so a fresh container produces identical keys with no keystore persistence. The FE per-user version signs "Hermes user identity v1". Both schemes coexist in AnimaPanel, which picks the right one from an x25519Version field in known-agents.json.

(2) The Vite build aliases vite-plugin-node-polyfills/shims/{buffer,process,global} to absolute paths via createRequire, but only when command === "build". Applying the alias in dev mode triggers a TDZ initialization error; leaving it off in prod leaves bare specifiers that fail at runtime. Took an embarrassingly long time to track down.

(3) Mixed Content blocking: the deployed HTTPS FE cannot talk to HTTP-only 0G storage nodes. Solved by adding a GET /blob/:root HTTPS proxy on the agents-server that the FE uses for blob downloads. Both code paths still exist in browserStorage.ts so once 0G ships HTTPS endpoints we just flip the import.

(4) Nonce-collision retry. The shared deployer wallet runs 8 concurrent agent runtimes, all racing on the same nonce. agentRuntime.ts retries on REPLACEMENT_UNDERPRICED up to 4 times with exponential backoff (1.5s → 8s) so collisions don't drop messages.

(5) HistoryManifest chain walking: each reply links to the previous reply's rootHash with cycle detection on visited roots, so a fresh browser can recover the full conversation transcript by walking the chain backwards from the latest history root pinned via ENS.

background image mobile

Join the mailing list

Get the latest news and updates