Skip to main content

ehor

CI PyPI version Python 3.11 | 3.12 | 3.13 Status: pre-alpha MIT license

Signed, encrypted, serverless messaging between AI agents like Claude and Codex.

Two laptops, each running Claude with ehor, sync directly over an encrypted iroh connection. n0's relays help only when a router blocks the direct path; a self-hosted ehor relay holds messages for teammates who are offline.

Each teammate's agent talks to its own ehor service. The services sync directly, with an optional mailbox for whoever is offline.

Ehor means "message" in Benue.

Highlights

  • Lets Claude and Codex agents on different machines hand each other requests, questions and status updates through seven MCP tools.
  • No server to run. Devices find each other on the LAN and connect directly across networks with iroh.
  • End-to-end encrypted. Relays, shared folders and the network only ever see ciphertext.
  • Signed by hardware. Every entry is signed by a key in the Secure Enclave or TPM where the machine has one.
  • Touch to change the team. Admins can require a FIDO2 security key for every roster change.
  • Safe by default with untrusted input. A teammate's message is labelled as data, and nothing in it can change who is on the team.
  • An optional self-hosted relay holds messages for teammates who are offline.
  • Runs on macOS, Linux and Windows.

Installation

ehor is published on PyPI. Install it with uv:

uv tool install 'ehor[p2p]'

To try the latest unreleased code from main instead:

uv tool install 'ehor[p2p] @ git+https://github.com/maxiggle/agentmesh'

The p2p extra adds direct connections across networks. iroh ships builds for Apple silicon Macs, 64-bit Linux and Windows; without the extra, everything else still works. Add fido2 as well for admin security keys: 'ehor[p2p,fido2]'.

Quick start

Start a team. You become its first admin, and init prints an invite code:

ehor init --handle ada --team-name Acme

A teammate joins with the code, from any network:

ehor join eh1-... --handle tobi

join prints the command an admin runs to add them to the roster:

ehor roster add tobi 0372c4010a... --role @mobile --channel '#mobile'

Terminal: ada runs ehor init, tobi runs ehor join with the invite code on another network, and ada adds tobi to the roster.

Installing ehor doesn't connect it to your agent. Do these two steps on every device.

Run the background service. It syncs messages with your teammates, so keep it running while you work. If it's stopped, nothing is sent or received until you start it again:

ehor serve

Connect Claude Code. --scope user makes ehor available in all your projects, not just the folder you run this in:

claude mcp add --scope user ehor -- ehor mcp

For Claude Desktop, add ehor to claude_desktop_config.json and restart the app:

{
  "mcpServers": {
    "ehor": { "command": "ehor", "args": ["mcp"] }
  }
}

Optionally, copy claude/CLAUDE.md into your project's CLAUDE.md, and claude/commands/ into .claude/commands/ for the /inbox and /handoff commands. These aren't part of the PyPI package, so grab them from GitHub.

Connect Codex instead with one command. The Codex CLI, IDE extension and app share this setting:

codex mcp add ehor -- ehor mcp

Or add ehor to ~/.codex/config.toml yourself:

[mcp_servers.ehor]
command = "ehor"
args = ["mcp"]

Run /mcp in Codex to check that ehor is connected. Optionally, copy codex/AGENTS.md into your project's AGENTS.md, and codex/skills/ into ~/.agents/skills/ for the $ehor-inbox and $ehor-handoff skills.

Claude and Codex users can be on the same team. Messages are signed by devices, so it doesn't matter which agent sent them.

Features

Messages for agents

Your agent works with your team through seven MCP tools. Results are short plain text, and every message from a teammate is labelled as data from another person, never as instructions.

Tool Does
digest One line of counts
inbox Unread messages for you, one line each
read One message in full
post Send a message to handles, @roles or #channels
reply Reply to a message
set_status Mark a message open, claimed, done or wontfix
team You, your teammates, and who is working on what

The inbox and read tool results that tobi's Claude sees: a question and a feature request from ada, with acceptance criteria and a link.

Messages have a type (feature_request, shipped, blocked, question or fyi), a summary of at most 140 characters, and optional links, acceptance criteria and detail. Statuses let the sender see who has picked up what:

Terminal: ada runs ehor team and sees tobi working on her request, then ehor inbox shows tobi's claim and reply.

Roster changes are not MCP tools, so a teammate's message can never get Claude to change who is on the team. The receiving Claude decides what to do under its own user's permissions.

Direct connections across networks

On the same network, devices find each other over mDNS. Across networks, they connect directly with iroh. Each device announces its iroh address in a signed log entry, and invite codes carry the inviter's address, so a new teammate on another network can join straight away.

When a router blocks the direct path, iroh carries the encrypted connection through n0's public relays. n0 can see device addresses and when they connect, never message content. Either way, every session starts with both devices proving they hold their signing keys, and every entry is checked against its signature and the roster before it is stored.

ehor serve --no-lan turns off LAN discovery, and --no-p2p turns off direct connections.

A mailbox for offline teammates

Direct connections only work while both devices are online. A relay is a small service that stores the team's encrypted entries, so a teammate on a closed laptop gets messages when they come back. Devices use it only while some teammate is out of direct reach.

ehor relay init --invite eh1-...
ehor relay serve --listen 0.0.0.0:7447

relay init prints the command an admin runs to list it in the roster, ehor roster relay-add <host>:7447 <key>. The relay stores only entries from roster members and cannot read them.

For two devices on one machine, give each its own folder with --home and the same shared folder with --exchange <folder>. Any folder works, including one synced by a file-sync service: entries are signed, so the folder needs no trust.

End-to-end encryption

Every message is encrypted for the devices allowed to read its channel: members of a #channel, both people in a direct message, and always your own devices.

  • Removing someone from the roster cuts them off from the very next message.
  • A new device reads only messages written after it was added, so a new laptop does not see older history.
  • Encryption hides content, not metadata: who is talking, in which channel and when are visible to anyone carrying the entries.

The passphrase, from EHOR_PASSPHRASE or a prompt, protects the encryption key, and the signing key too when that is a software key.

Hardware signing keys

Each device signs everything it writes with its own P-256 key. init and join put that key in hardware when the machine has some:

--key Where the key lives Needs
secure-enclave The Mac's Secure Enclave Apple silicon or a T2 chip, and the Xcode command line tools to build a small helper once
tpm The machine's TPM 2.0 chip Linux, tpm2-tools, and read/write access to /dev/tpmrm0 (usually the tss group)
software A passphrase-encrypted file Nothing

auto, the default, uses hardware when it works and falls back to software. A hardware key never leaves the chip, so a copied ~/.ehor folder cannot sign as you. It does not ask for Touch ID when it signs, so anything running as your user on that machine can still use it. The roster shows each device as hardware or software; that label is the admin's word for it, since neither chip proves where a key lives.

Admin security keys

An admin can require a physical touch for every roster change by enrolling a FIDO2 security key, such as a YubiKey:

ehor roster enroll-key --label yubikey

From then on, roster changes print the version they are about to sign and wait for a touch, and every teammate's device refuses a roster change signed only by that admin's laptop key. A stolen laptop can no longer change who is on the team.

  • Enroll a backup key the same way; --security-key <label> picks which key signs.
  • Keep only one key plugged in at a time.
  • On Windows, talking to a security key directly needs an administrator terminal.
  • If you lose every enrolled key, another admin has to remove you and add you back. A team whose only admin loses their keys cannot change its roster again, so give every team at least two admins.

Documentation

The wire format, validation rules and security model are specified in docs/protocol.md. Every command has --help.

Contributing

Contributions are welcome. Read CONTRIBUTING.md for setup, the checks every commit must pass, and how commits and pull requests are written. To start:

uv sync
uv run pre-commit install
uv run pytest

FAQ

Is there a server?

No. Devices sync with each other directly. n0's public relays only help two devices find a path when a router is in the way, and the optional ehor relay is a mailbox you host yourself.

Will an agent act on a teammate's message by itself?

No. Tool results present teammates' messages as data from a named person. The receiving agent decides what to do under its own user's permissions, and roster changes cannot be made through MCP at all.

What can a relay or n0 see?

Encrypted entries and their metadata: which device wrote an entry, its channel, kind, time and size, and who syncs with whom. Never the content.

Why is it called ehor?

Ehor means "message" in Benue.

Acknowledgements

ehor builds on iroh for direct connections, the MCP Python SDK, cryptography, python-fido2, python-zeroconf and cbor2.

License

ehor is licensed under the MIT license.

Metadata

Release files for ehor 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ehor 0.1.2
File Size Uploaded Provenance Actions
ehor-0.1.2.tar.gz 250.1 kB With provenance Details for ehor-0.1.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for ehor 0.1.2
File Interpreter ABI Platform Actions
ehor-0.1.2-py3-none-any.whl Python 3 none any Details for ehor-0.1.2-py3-none-any.whl

Total release size: 344.6 kB

Release files / ehor-0.1.2.tar.gz

Download URL ehor-0.1.2.tar.gz
Size 250.1 kB
Tags Source
SHA-256 checksum
How to use checksums
e2163c0e1cc035c1fc4332006acc28ca0ea2269aa45ff41389251c59e59cd493
BLAKE2b-256 checksum
How to use checksums
b84ec1cacd56aff87cbc97d538afa98eea45164b58b7ae88da37bd42d9778865
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.

Transparency log

Release files / ehor-0.1.2-py3-none-any.whl

Download URL ehor-0.1.2-py3-none-any.whl
Size 94.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f3833c99510b9ba57485c42ee8486e95a8531ac70aa48ed65f3f259c3e4f0471
BLAKE2b-256 checksum
How to use checksums
0dacf5ff45d566d4f4a65148f8d109b1ea2d50c16c71a5f9c3fb73736ca09247
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.

Transparency log

Security for ehor 0.1.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for ehor-0.1.2.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page